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
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
from langchain_core.prompts import ChatPromptTemplate
|
|
2
|
+
from acra.agents.llm import llm
|
|
3
|
+
|
|
4
|
+
from acra.schemas.critic_schema import CriticOutput
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
#create critic chain
|
|
8
|
+
|
|
9
|
+
def create_critic_chain():
|
|
10
|
+
"""
|
|
11
|
+
Creates the Critic Agent chain.
|
|
12
|
+
|
|
13
|
+
Responsibilities:
|
|
14
|
+
- Review generated code
|
|
15
|
+
- Analyze execution results
|
|
16
|
+
- Detect vulnerabilities
|
|
17
|
+
- Evaluate architecture quality
|
|
18
|
+
- Validate implementation correctness
|
|
19
|
+
- Suggest improvements
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
model = llm()
|
|
23
|
+
|
|
24
|
+
critic_prompt = ChatPromptTemplate.from_messages([
|
|
25
|
+
|
|
26
|
+
(
|
|
27
|
+
"system",
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
You are the Critic Agent inside an autonomous AI system called OMNIAGENT.
|
|
31
|
+
|
|
32
|
+
You are a senior AI software architect and security reviewer.
|
|
33
|
+
|
|
34
|
+
Your responsibilities:
|
|
35
|
+
- Review generated code quality
|
|
36
|
+
- Evaluate runtime execution results
|
|
37
|
+
- Detect architectural problems
|
|
38
|
+
- Identify security vulnerabilities
|
|
39
|
+
- Detect hallucinated libraries/APIs
|
|
40
|
+
- Validate implementation correctness
|
|
41
|
+
- Suggest improvements
|
|
42
|
+
- Decide workflow continuation
|
|
43
|
+
|
|
44
|
+
==================================================
|
|
45
|
+
FIRST: DETECT PROJECT TYPE
|
|
46
|
+
==================================================
|
|
47
|
+
|
|
48
|
+
BEFORE REVIEWING, detect if this is a web application:
|
|
49
|
+
|
|
50
|
+
WEB APPLICATION KEYWORDS:
|
|
51
|
+
- Flask, FastAPI, Django, Tornado, etc.
|
|
52
|
+
- User request mentions: website, web app, web interface, portfolio, blog, dashboard
|
|
53
|
+
- Generated files include: templates/, static/, html files, CSS, JavaScript
|
|
54
|
+
- Main app file uses: Flask(), FastAPI(), Django
|
|
55
|
+
|
|
56
|
+
If this is a WEB APPLICATION:
|
|
57
|
+
- Follow the "WEB SERVER EXECUTION RULES" section below
|
|
58
|
+
- Evaluate based on code structure, NOT execution_success
|
|
59
|
+
- Remember: web apps cannot finish executing (servers block indefinitely)
|
|
60
|
+
|
|
61
|
+
==================================================
|
|
62
|
+
YOUR ROLE
|
|
63
|
+
==================================================
|
|
64
|
+
|
|
65
|
+
You are NOT a coding agent.
|
|
66
|
+
|
|
67
|
+
You DO NOT generate code directly.
|
|
68
|
+
|
|
69
|
+
You ONLY:
|
|
70
|
+
- analyze
|
|
71
|
+
- evaluate
|
|
72
|
+
- validate
|
|
73
|
+
- critique
|
|
74
|
+
- review
|
|
75
|
+
|
|
76
|
+
==================================================
|
|
77
|
+
REVIEW OBJECTIVES
|
|
78
|
+
==================================================
|
|
79
|
+
|
|
80
|
+
You must evaluate:
|
|
81
|
+
|
|
82
|
+
1. CODE QUALITY
|
|
83
|
+
- readability
|
|
84
|
+
- maintainability
|
|
85
|
+
- modularity
|
|
86
|
+
- consistency
|
|
87
|
+
|
|
88
|
+
2. EXECUTION QUALITY
|
|
89
|
+
- runtime success
|
|
90
|
+
- runtime stability
|
|
91
|
+
- error handling
|
|
92
|
+
|
|
93
|
+
3. ARCHITECTURE
|
|
94
|
+
- clean structure
|
|
95
|
+
- scalability
|
|
96
|
+
- separation of concerns
|
|
97
|
+
|
|
98
|
+
4. SECURITY
|
|
99
|
+
- hardcoded secrets
|
|
100
|
+
- dangerous subprocess usage
|
|
101
|
+
- shell injection risks
|
|
102
|
+
- unsafe eval/exec usage
|
|
103
|
+
- insecure configurations
|
|
104
|
+
|
|
105
|
+
5. RELIABILITY
|
|
106
|
+
- missing dependencies
|
|
107
|
+
- broken imports
|
|
108
|
+
- incomplete implementations
|
|
109
|
+
|
|
110
|
+
6. TESTING
|
|
111
|
+
- validation logic
|
|
112
|
+
- testability
|
|
113
|
+
- coverage awareness
|
|
114
|
+
|
|
115
|
+
==================================================
|
|
116
|
+
CURRENT WORKFLOW STATE
|
|
117
|
+
==================================================
|
|
118
|
+
|
|
119
|
+
USER REQUEST:
|
|
120
|
+
{user_request}
|
|
121
|
+
|
|
122
|
+
CURRENT TASK:
|
|
123
|
+
{current_step}
|
|
124
|
+
|
|
125
|
+
GENERATED FILES:
|
|
126
|
+
{generated_files}
|
|
127
|
+
|
|
128
|
+
EXECUTION SUCCESS:
|
|
129
|
+
{execution_success}
|
|
130
|
+
|
|
131
|
+
EXECUTION LOGS:
|
|
132
|
+
{execution_logs}
|
|
133
|
+
|
|
134
|
+
EXECUTION OUTPUT:
|
|
135
|
+
{execution_output}
|
|
136
|
+
|
|
137
|
+
ERROR MESSAGE:
|
|
138
|
+
{error_message}
|
|
139
|
+
|
|
140
|
+
RETRY COUNT:
|
|
141
|
+
{retry_count}
|
|
142
|
+
|
|
143
|
+
==================================================
|
|
144
|
+
REVIEW RULES
|
|
145
|
+
==================================================
|
|
146
|
+
|
|
147
|
+
- If execution failed:
|
|
148
|
+
strongly recommend returning to coder.
|
|
149
|
+
|
|
150
|
+
- If security vulnerabilities exist:
|
|
151
|
+
mark review_status as "unsafe".
|
|
152
|
+
|
|
153
|
+
- If implementation is acceptable:
|
|
154
|
+
mark review_status as "approved".
|
|
155
|
+
|
|
156
|
+
- If implementation works but needs refinement:
|
|
157
|
+
mark review_status as "needs_improvement".
|
|
158
|
+
|
|
159
|
+
- If implementation is fundamentally broken:
|
|
160
|
+
mark review_status as "failed".
|
|
161
|
+
|
|
162
|
+
==================================================
|
|
163
|
+
WEB SERVER EXECUTION RULES
|
|
164
|
+
==================================================
|
|
165
|
+
|
|
166
|
+
CRITICAL: Web applications (Flask, FastAPI, Django, etc.) CANNOT be validated
|
|
167
|
+
by running them to completion. They run indefinitely by design.
|
|
168
|
+
|
|
169
|
+
When evaluating web applications:
|
|
170
|
+
|
|
171
|
+
1. IGNORE execution_success = False if it's a web server app
|
|
172
|
+
- Validation script success means the app is structurally sound
|
|
173
|
+
|
|
174
|
+
2. EVALUATE WEB APPS BASED ON:
|
|
175
|
+
- Directory structure (templates/, static/ folders present)
|
|
176
|
+
- HTML templates properly structured and syntactically valid
|
|
177
|
+
- CSS files with proper styling rules
|
|
178
|
+
- JavaScript files present and valid
|
|
179
|
+
- Flask/FastAPI application entry point (app.py or main.py) well-formed
|
|
180
|
+
- requirements.txt listing correct dependencies
|
|
181
|
+
- All imports and syntax are valid
|
|
182
|
+
|
|
183
|
+
3. SCORING WEB APPLICATIONS:
|
|
184
|
+
- Score 8-9: Complete structure, all files present, no security issues, follows conventions
|
|
185
|
+
- Score 7-8: Most files present, minor structural issues, good architecture
|
|
186
|
+
- Score below 7: Missing critical files, incomplete implementation, or security risks
|
|
187
|
+
|
|
188
|
+
4. APPROVE WEB APPLICATIONS IF:
|
|
189
|
+
- All expected files are generated (templates, static assets, main app file)
|
|
190
|
+
- No hardcoded secrets or security vulnerabilities
|
|
191
|
+
- Proper folder structure for Flask/FastAPI (templates/, static/ dirs)
|
|
192
|
+
- Requirements.txt exists with valid syntax
|
|
193
|
+
- Application file has valid Flask/FastAPI app definition
|
|
194
|
+
|
|
195
|
+
==================================================
|
|
196
|
+
NEXT AGENT RULES
|
|
197
|
+
==================================================
|
|
198
|
+
|
|
199
|
+
- approved → end
|
|
200
|
+
- needs_improvement → coder
|
|
201
|
+
- failed → coder
|
|
202
|
+
- unsafe → human
|
|
203
|
+
|
|
204
|
+
==================================================
|
|
205
|
+
OUTPUT REQUIREMENTS
|
|
206
|
+
==================================================
|
|
207
|
+
|
|
208
|
+
Return ONLY structured output.
|
|
209
|
+
|
|
210
|
+
==================================================
|
|
211
|
+
QUALITY SCORING
|
|
212
|
+
==================================================
|
|
213
|
+
|
|
214
|
+
Score from 0 → 10
|
|
215
|
+
|
|
216
|
+
STANDARD PROJECTS (CLI, Libraries, Scripts):
|
|
217
|
+
0-3: Broken or unsafe implementation
|
|
218
|
+
4-6: Partially working but significant issues
|
|
219
|
+
7-8: Good implementation with minor improvements needed
|
|
220
|
+
9-10: Production-quality implementation
|
|
221
|
+
|
|
222
|
+
WEB APPLICATIONS (Flask, FastAPI, Django, etc.):
|
|
223
|
+
0-3: Broken structure, missing critical files
|
|
224
|
+
4-5: Incomplete - major components missing
|
|
225
|
+
6-7: Functional structure but needs refinement
|
|
226
|
+
8-9: Well-structured, all components present, good architecture
|
|
227
|
+
10: Production-ready with perfect structure and styling
|
|
228
|
+
|
|
229
|
+
CRITICAL: For web apps that validated successfully (exit code 0),
|
|
230
|
+
minimum score should be 7-8 if structure is correct.
|
|
231
|
+
|
|
232
|
+
==================================================
|
|
233
|
+
IMPORTANT
|
|
234
|
+
==================================================
|
|
235
|
+
|
|
236
|
+
Be extremely critical and realistic.
|
|
237
|
+
|
|
238
|
+
Do NOT approve weak implementations.
|
|
239
|
+
|
|
240
|
+
Your job is to ensure:
|
|
241
|
+
- reliability
|
|
242
|
+
- correctness
|
|
243
|
+
- maintainability
|
|
244
|
+
- security
|
|
245
|
+
"""
|
|
246
|
+
),
|
|
247
|
+
|
|
248
|
+
(
|
|
249
|
+
"human",
|
|
250
|
+
|
|
251
|
+
"""
|
|
252
|
+
Review the generated implementation and determine workflow quality.
|
|
253
|
+
"""
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
])
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
#structure output
|
|
260
|
+
|
|
261
|
+
structured_llm = model.with_structured_output(
|
|
262
|
+
CriticOutput
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
#return chain
|
|
267
|
+
|
|
268
|
+
return critic_prompt | structured_llm
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import time
|
|
3
|
+
import shlex
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import List, Union
|
|
7
|
+
from acra.schemas.execution_schema import ExecutionResult
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
Command = Union[str, List[str]]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _normalize_command(command: Command) -> List[str]:
|
|
14
|
+
if isinstance(command, str):
|
|
15
|
+
return shlex.split(command)
|
|
16
|
+
return command
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def run_in_docker(
|
|
20
|
+
project_path: str,
|
|
21
|
+
command: Command = "python app.py",
|
|
22
|
+
timeout: int = 60,
|
|
23
|
+
) -> ExecutionResult:
|
|
24
|
+
"""
|
|
25
|
+
executes generated prject in side docker container."""
|
|
26
|
+
|
|
27
|
+
start_time = time.time()
|
|
28
|
+
resolved_project_path = str(Path(project_path).resolve())
|
|
29
|
+
command_args = _normalize_command(command)
|
|
30
|
+
docker_command = [
|
|
31
|
+
"docker",
|
|
32
|
+
"run",
|
|
33
|
+
"--rm",
|
|
34
|
+
"--network",
|
|
35
|
+
"none",
|
|
36
|
+
"--user",
|
|
37
|
+
f"{os.getuid()}:{os.getgid()}",
|
|
38
|
+
"--memory",
|
|
39
|
+
"256m",
|
|
40
|
+
"--cpus",
|
|
41
|
+
"1",
|
|
42
|
+
"--pids-limit",
|
|
43
|
+
"128",
|
|
44
|
+
"--cap-drop",
|
|
45
|
+
"ALL",
|
|
46
|
+
"--security-opt",
|
|
47
|
+
"no-new-privileges",
|
|
48
|
+
"-v",
|
|
49
|
+
f"{resolved_project_path}:/app:rw",
|
|
50
|
+
"-w",
|
|
51
|
+
"/app",
|
|
52
|
+
"python:3.11",
|
|
53
|
+
*command_args
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
result = subprocess.run(
|
|
58
|
+
docker_command,
|
|
59
|
+
capture_output=True,
|
|
60
|
+
text=True,
|
|
61
|
+
timeout=timeout
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
execution_time = time.time() - start_time
|
|
65
|
+
success = result.returncode == 0
|
|
66
|
+
|
|
67
|
+
return ExecutionResult(
|
|
68
|
+
execution_status=(
|
|
69
|
+
"success" if success else "failed"
|
|
70
|
+
),
|
|
71
|
+
|
|
72
|
+
execution_success=success,
|
|
73
|
+
|
|
74
|
+
stdout=result.stdout,
|
|
75
|
+
|
|
76
|
+
stderr=result.stderr,
|
|
77
|
+
|
|
78
|
+
error_message=(
|
|
79
|
+
result.stderr if not success else None
|
|
80
|
+
),
|
|
81
|
+
|
|
82
|
+
executed_command=" ".join(command_args),
|
|
83
|
+
|
|
84
|
+
execution_time=execution_time,
|
|
85
|
+
|
|
86
|
+
next_agent=(
|
|
87
|
+
"critic" if success else "coder"
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
except subprocess.TimeoutExpired:
|
|
92
|
+
return ExecutionResult(
|
|
93
|
+
execution_status="timeout",
|
|
94
|
+
execution_success=False,
|
|
95
|
+
stdout="",
|
|
96
|
+
stderr="Execution timed out.",
|
|
97
|
+
error_message="Execution exceeded the timeout limit.",
|
|
98
|
+
executed_command=" ".join(command_args),
|
|
99
|
+
execution_time=timeout,
|
|
100
|
+
next_agent="coder"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
except Exception as e:
|
|
104
|
+
|
|
105
|
+
return ExecutionResult(
|
|
106
|
+
execution_status="crashed",
|
|
107
|
+
execution_success=False,
|
|
108
|
+
stdout="",
|
|
109
|
+
stderr=str(e),
|
|
110
|
+
error_message=str(e),
|
|
111
|
+
executed_command=" ".join(command_args),
|
|
112
|
+
execution_time=time.time() - start_time,
|
|
113
|
+
next_agent="human"
|
|
114
|
+
)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
from langchain_core.messages import AIMessage
|
|
3
|
+
from acra.graph.state import AgentState
|
|
4
|
+
|
|
5
|
+
from acra.agents.executor.sandbox_runner import (
|
|
6
|
+
execute_generated_project
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
def executor_agent(state: AgentState) -> Dict:
|
|
10
|
+
"""
|
|
11
|
+
Execution Agent
|
|
12
|
+
|
|
13
|
+
Responsibilities:
|
|
14
|
+
- Save generated files.
|
|
15
|
+
- Execute generated code
|
|
16
|
+
- Capture runtime logs/errors
|
|
17
|
+
- Detect failures
|
|
18
|
+
- Route workflow intelligently
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# Extract state
|
|
22
|
+
generated_files = state.get(
|
|
23
|
+
"generated_files",
|
|
24
|
+
{}
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
project_name = state.get(
|
|
28
|
+
"project_name",
|
|
29
|
+
"current_project"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
entry_point = state.get("entry_point", "app.py") or "app.py"
|
|
33
|
+
|
|
34
|
+
retry_count = state.get(
|
|
35
|
+
"retry_count",
|
|
36
|
+
0
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
#Validate generated files
|
|
40
|
+
|
|
41
|
+
if not generated_files:
|
|
42
|
+
return {
|
|
43
|
+
"messages": [
|
|
44
|
+
AIMessage(
|
|
45
|
+
content=(
|
|
46
|
+
"Execution Agent: "
|
|
47
|
+
"No generated files found."
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
],
|
|
51
|
+
|
|
52
|
+
"execution_success": False,
|
|
53
|
+
|
|
54
|
+
"error_message": (
|
|
55
|
+
"No files available for execution."
|
|
56
|
+
),
|
|
57
|
+
|
|
58
|
+
"next_agent": "coder",
|
|
59
|
+
"current_agent": "executor",
|
|
60
|
+
"project_name": project_name,
|
|
61
|
+
|
|
62
|
+
"retry_count": retry_count + 1
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Execute generated project
|
|
67
|
+
execution_result = execute_generated_project(
|
|
68
|
+
generated_files=generated_files,
|
|
69
|
+
project_name=project_name,
|
|
70
|
+
entry_point=entry_point,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Detect if this is a web server app
|
|
74
|
+
all_content = " ".join(generated_files.values())
|
|
75
|
+
is_web_server = any(kw in all_content for kw in [
|
|
76
|
+
"app.run(", "uvicorn", "Flask(", "FastAPI(", "django", "tornado"
|
|
77
|
+
])
|
|
78
|
+
|
|
79
|
+
# For web servers that validate successfully, consider it a success
|
|
80
|
+
# (validation passes = syntax OK + dependencies OK)
|
|
81
|
+
execution_success = execution_result.execution_success
|
|
82
|
+
if is_web_server and execution_result.execution_status == "success":
|
|
83
|
+
execution_success = True
|
|
84
|
+
|
|
85
|
+
if execution_success:
|
|
86
|
+
if is_web_server:
|
|
87
|
+
executor_message = (
|
|
88
|
+
"Execution Agent: "
|
|
89
|
+
"Web application structure validated successfully.\n\n"
|
|
90
|
+
f"Execution time: {execution_result.execution_time:.2f} seconds."
|
|
91
|
+
)
|
|
92
|
+
else:
|
|
93
|
+
executor_message = (
|
|
94
|
+
"Execution Agent: "
|
|
95
|
+
"Project executed successfully.\n\n"
|
|
96
|
+
f"Execution time: {execution_result.execution_time:.2f} seconds."
|
|
97
|
+
)
|
|
98
|
+
next_agent = "critic"
|
|
99
|
+
else:
|
|
100
|
+
executor_message = (
|
|
101
|
+
"Execution Agent: "
|
|
102
|
+
"Project execution failed.\n\n"
|
|
103
|
+
f"Error: {execution_result.error_message or 'Unknown error.'}"
|
|
104
|
+
)
|
|
105
|
+
next_agent = "coder" if retry_count < 3 else "critic"
|
|
106
|
+
|
|
107
|
+
# Detect if failure was a web server timeout (not a real code error)
|
|
108
|
+
is_timeout = execution_result.execution_status == "timeout"
|
|
109
|
+
is_expected_timeout = is_web_server and is_timeout
|
|
110
|
+
|
|
111
|
+
# Only increment retry_count for real failures, not expected web server timeouts
|
|
112
|
+
retry_increment = 0 if is_expected_timeout else (
|
|
113
|
+
0 if execution_success else 1
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Return updated state
|
|
117
|
+
return {
|
|
118
|
+
"messages": [
|
|
119
|
+
AIMessage(
|
|
120
|
+
content=executor_message
|
|
121
|
+
)
|
|
122
|
+
],
|
|
123
|
+
|
|
124
|
+
#execution results
|
|
125
|
+
"execution_success": execution_success,
|
|
126
|
+
"execution_status": execution_result.execution_status,
|
|
127
|
+
"execution_output": execution_result.stdout,
|
|
128
|
+
"execution_logs": (
|
|
129
|
+
f"STDOUT:\n{execution_result.stdout}\n\n"
|
|
130
|
+
f"STDERR:\n{execution_result.stderr}"
|
|
131
|
+
),
|
|
132
|
+
"error_message": execution_result.error_message,
|
|
133
|
+
"execution_time": execution_result.execution_time,
|
|
134
|
+
|
|
135
|
+
#workflow
|
|
136
|
+
"next_agent": next_agent,
|
|
137
|
+
"current_agent": "executor",
|
|
138
|
+
"project_name": project_name,
|
|
139
|
+
|
|
140
|
+
#retry tracking
|
|
141
|
+
"retry_count": retry_count + retry_increment
|
|
142
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Dict
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from acra.agents.executor.docker_runner import run_in_docker
|
|
6
|
+
from acra.schemas.execution_schema import ExecutionResult
|
|
7
|
+
from acra.config import GENERATED_PROJECT_DIR
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _safe_child_path(base_path: Path, relative_path: str) -> Path:
|
|
14
|
+
"""
|
|
15
|
+
Resolve a user/model supplied child path and ensure it stays under base_path.
|
|
16
|
+
"""
|
|
17
|
+
candidate = Path(relative_path)
|
|
18
|
+
if candidate.is_absolute() or ".." in candidate.parts:
|
|
19
|
+
raise ValueError(f"Unsafe generated file path: {relative_path}")
|
|
20
|
+
|
|
21
|
+
resolved_base = base_path.resolve()
|
|
22
|
+
resolved_candidate = (resolved_base / candidate).resolve()
|
|
23
|
+
if not resolved_candidate.is_relative_to(resolved_base):
|
|
24
|
+
raise ValueError(f"Generated file escapes project directory: {relative_path}")
|
|
25
|
+
|
|
26
|
+
return resolved_candidate
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def save_generated_files(
|
|
30
|
+
generated_files: Dict[str, str],
|
|
31
|
+
project_name: str = "current_project"
|
|
32
|
+
) -> str:
|
|
33
|
+
"""
|
|
34
|
+
Save generated files to sandbox project directory.
|
|
35
|
+
"""
|
|
36
|
+
project_path = _safe_child_path(
|
|
37
|
+
Path(GENERATED_PROJECT_DIR),
|
|
38
|
+
project_name or "current_project"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
logger.info(f"Creating project directory: {project_path}")
|
|
42
|
+
|
|
43
|
+
project_path.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
|
|
45
|
+
for filename, content in generated_files.items():
|
|
46
|
+
|
|
47
|
+
file_path = _safe_child_path(project_path, filename)
|
|
48
|
+
|
|
49
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
|
|
51
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
52
|
+
f.write(content)
|
|
53
|
+
|
|
54
|
+
logger.debug(f"Saved file: {file_path} ({len(content)} bytes)")
|
|
55
|
+
|
|
56
|
+
logger.info(f"Project '{project_name}' saved with {len(generated_files)} files")
|
|
57
|
+
|
|
58
|
+
return str(project_path)
|
|
59
|
+
|
|
60
|
+
def execute_generated_project(
|
|
61
|
+
generated_files: Dict[str, str],
|
|
62
|
+
project_name: str = "current_project",
|
|
63
|
+
command: str = None,
|
|
64
|
+
entry_point: str = "app.py"
|
|
65
|
+
) -> ExecutionResult:
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
Save generated project and execute inside a sandbox.
|
|
69
|
+
Auto-detects web server frameworks and validates them without running the server.
|
|
70
|
+
"""
|
|
71
|
+
project_path = save_generated_files(
|
|
72
|
+
generated_files=generated_files,
|
|
73
|
+
project_name=project_name
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Auto-detect web server frameworks
|
|
77
|
+
all_content = " ".join(generated_files.values())
|
|
78
|
+
is_web_server = any(kw in all_content for kw in [
|
|
79
|
+
"app.run(", "uvicorn", "Flask(", "FastAPI(", "django", "tornado"
|
|
80
|
+
])
|
|
81
|
+
|
|
82
|
+
if command is None:
|
|
83
|
+
if is_web_server:
|
|
84
|
+
# For web servers, create and run a validation script
|
|
85
|
+
validation_script = """import ast
|
|
86
|
+
import sys
|
|
87
|
+
from pathlib import Path
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
from pip._vendor.packaging.requirements import Requirement
|
|
91
|
+
except Exception:
|
|
92
|
+
Requirement = None
|
|
93
|
+
|
|
94
|
+
# Validate Python syntax for all .py files recursively.
|
|
95
|
+
errors = []
|
|
96
|
+
for path in Path('.').rglob('*.py'):
|
|
97
|
+
if any(part.startswith('.') for part in path.parts):
|
|
98
|
+
continue
|
|
99
|
+
try:
|
|
100
|
+
source = path.read_text(encoding='utf-8')
|
|
101
|
+
ast.parse(source, filename=str(path))
|
|
102
|
+
except SyntaxError as e:
|
|
103
|
+
errors.append(f"{path}: {e}")
|
|
104
|
+
except UnicodeDecodeError as e:
|
|
105
|
+
errors.append(f"{path}: {e}")
|
|
106
|
+
|
|
107
|
+
requirements = Path('requirements.txt')
|
|
108
|
+
if requirements.exists():
|
|
109
|
+
for line_number, line in enumerate(requirements.read_text(encoding='utf-8').splitlines(), 1):
|
|
110
|
+
stripped = line.strip()
|
|
111
|
+
if not stripped or stripped.startswith('#'):
|
|
112
|
+
continue
|
|
113
|
+
if Requirement is None:
|
|
114
|
+
continue
|
|
115
|
+
try:
|
|
116
|
+
Requirement(stripped)
|
|
117
|
+
except Exception as e:
|
|
118
|
+
errors.append(f"requirements.txt:{line_number}: invalid requirement {stripped!r}: {e}")
|
|
119
|
+
|
|
120
|
+
if errors:
|
|
121
|
+
print("Validation errors found:")
|
|
122
|
+
for err in errors:
|
|
123
|
+
print(f" {err}")
|
|
124
|
+
sys.exit(1)
|
|
125
|
+
|
|
126
|
+
print("Web application validation passed: all files have valid syntax")
|
|
127
|
+
sys.exit(0)
|
|
128
|
+
"""
|
|
129
|
+
# Save validation script to project
|
|
130
|
+
validation_path = _safe_child_path(Path(project_path), "_validate.py")
|
|
131
|
+
with open(validation_path, "w", encoding="utf-8") as f:
|
|
132
|
+
f.write(validation_script)
|
|
133
|
+
|
|
134
|
+
command = "python _validate.py"
|
|
135
|
+
else:
|
|
136
|
+
resolved_entry_point = (entry_point or "app.py").strip() or "app.py"
|
|
137
|
+
command = f"python {resolved_entry_point}"
|
|
138
|
+
|
|
139
|
+
result = run_in_docker(
|
|
140
|
+
project_path=project_path,
|
|
141
|
+
command=command
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
return result
|
|
File without changes
|
|
Binary file
|
|
Binary file
|