agentsite 0.0.2.dev1__tar.gz → 0.0.3.dev1__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.
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/PKG-INFO +1 -1
- agentsite-0.0.3.dev1/agentsite/agents/designer.py +41 -0
- agentsite-0.0.3.dev1/agentsite/agents/developer.py +75 -0
- agentsite-0.0.3.dev1/agentsite/agents/pm.py +41 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/engine/pipeline.py +158 -11
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite.egg-info/PKG-INFO +1 -1
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/pyproject.toml +1 -1
- agentsite-0.0.2.dev1/agentsite/agents/designer.py +0 -20
- agentsite-0.0.2.dev1/agentsite/agents/developer.py +0 -27
- agentsite-0.0.2.dev1/agentsite/agents/pm.py +0 -20
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/README.md +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/__init__.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/agents/__init__.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/agents/orchestrator.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/agents/personas.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/agents/reviewer.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/agents/tools.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/api/__init__.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/api/app.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/api/deps.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/api/routes/__init__.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/api/routes/agents.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/api/routes/assets.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/api/routes/generate.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/api/routes/models.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/api/routes/preview.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/api/routes/projects.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/api/routes/providers.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/api/websocket.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/cli.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/config.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/engine/__init__.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/engine/asset_handler.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/engine/gemini_patch.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/engine/project_manager.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/models.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/storage/__init__.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/storage/database.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite/storage/repository.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite.egg-info/SOURCES.txt +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite.egg-info/dependency_links.txt +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite.egg-info/entry_points.txt +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite.egg-info/requires.txt +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/agentsite.egg-info/top_level.txt +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/setup.cfg +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/tests/test_agents.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/tests/test_api.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/tests/test_models.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/tests/test_project_manager.py +0 -0
- {agentsite-0.0.2.dev1 → agentsite-0.0.3.dev1}/tests/test_storage.py +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Designer agent factory."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from prompture import Agent
|
|
6
|
+
|
|
7
|
+
from ..models import StyleSpec
|
|
8
|
+
from .personas import DESIGNER_PERSONA
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def create_designer_agent(model: str) -> Agent:
|
|
12
|
+
"""Create the Designer agent that produces a StyleSpec."""
|
|
13
|
+
return Agent(
|
|
14
|
+
model,
|
|
15
|
+
system_prompt=DESIGNER_PERSONA,
|
|
16
|
+
output_type=StyleSpec,
|
|
17
|
+
name="designer",
|
|
18
|
+
description="Defines visual design system (colors, fonts, spacing)",
|
|
19
|
+
output_key="style_spec",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_designer_agent_plain(model: str) -> Agent:
|
|
24
|
+
"""Create a Designer agent WITHOUT output_type for models that don't support structured output.
|
|
25
|
+
|
|
26
|
+
Uses explicit JSON instructions in the system prompt instead of schema enforcement.
|
|
27
|
+
The caller is responsible for parsing the JSON output manually.
|
|
28
|
+
"""
|
|
29
|
+
json_schema = StyleSpec.model_json_schema()
|
|
30
|
+
return Agent(
|
|
31
|
+
model,
|
|
32
|
+
system_prompt=(
|
|
33
|
+
DESIGNER_PERSONA.system_prompt
|
|
34
|
+
+ "\n\nIMPORTANT: You MUST respond with ONLY a valid JSON object matching this schema:\n"
|
|
35
|
+
+ f"```json\n{__import__('json').dumps(json_schema, indent=2)}\n```\n"
|
|
36
|
+
"Do NOT include any text before or after the JSON. Return ONLY the JSON object."
|
|
37
|
+
),
|
|
38
|
+
name="designer",
|
|
39
|
+
description="Defines visual design system (plain text mode)",
|
|
40
|
+
output_key="style_spec",
|
|
41
|
+
)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Developer agent factory."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from prompture import Agent
|
|
6
|
+
|
|
7
|
+
from .personas import DEVELOPER_PERSONA
|
|
8
|
+
from .tools import list_files, read_file, write_file
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def create_developer_agent(model: str) -> Agent:
|
|
12
|
+
"""Create the Developer agent that generates page files.
|
|
13
|
+
|
|
14
|
+
Note: No ``output_type`` is set because the developer writes files
|
|
15
|
+
via the ``write_file`` tool. Forcing structured-output parsing on the
|
|
16
|
+
final text response causes failures when the LLM returns empty text
|
|
17
|
+
after finishing its tool calls. The pipeline already handles file
|
|
18
|
+
extraction from both tool-written files and raw output text.
|
|
19
|
+
"""
|
|
20
|
+
return Agent(
|
|
21
|
+
model,
|
|
22
|
+
system_prompt=DEVELOPER_PERSONA,
|
|
23
|
+
tools=[write_file, read_file, list_files],
|
|
24
|
+
name="developer",
|
|
25
|
+
description="Generates HTML/CSS/JS files for each page",
|
|
26
|
+
output_key="page_output",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def create_developer_agent_plain(model: str) -> Agent:
|
|
31
|
+
"""Create a Developer agent WITHOUT tools for models that don't support tool calling.
|
|
32
|
+
|
|
33
|
+
Instead of using write_file/read_file tools, this agent outputs file contents
|
|
34
|
+
directly in its response using markdown fenced code blocks. The pipeline's
|
|
35
|
+
existing fallback extraction logic (_extract_fenced_blocks, _try_extract_raw_html)
|
|
36
|
+
handles parsing the output into files on disk.
|
|
37
|
+
"""
|
|
38
|
+
return Agent(
|
|
39
|
+
model,
|
|
40
|
+
system_prompt=(
|
|
41
|
+
"You are an expert frontend developer. You build complete, production-ready "
|
|
42
|
+
"web pages using semantic HTML5, modern CSS, and vanilla JavaScript.\n\n"
|
|
43
|
+
"WORKFLOW — you MUST follow this exact output format:\n"
|
|
44
|
+
"Output each file using markdown fenced code blocks with the language tag.\n"
|
|
45
|
+
"You MUST generate at least an index.html file.\n\n"
|
|
46
|
+
"Example output format:\n"
|
|
47
|
+
"```html\n"
|
|
48
|
+
"<!DOCTYPE html>\n"
|
|
49
|
+
"<html>...</html>\n"
|
|
50
|
+
"```\n\n"
|
|
51
|
+
"```css\n"
|
|
52
|
+
"/* styles.css */\n"
|
|
53
|
+
"body { ... }\n"
|
|
54
|
+
"```\n\n"
|
|
55
|
+
"```javascript\n"
|
|
56
|
+
"// script.js\n"
|
|
57
|
+
"document.addEventListener('DOMContentLoaded', ...);\n"
|
|
58
|
+
"```\n\n"
|
|
59
|
+
"Requirements:\n"
|
|
60
|
+
"- Write clean, semantic HTML with proper heading hierarchy\n"
|
|
61
|
+
"- Use CSS custom properties for theming (colors, fonts, spacing)\n"
|
|
62
|
+
"- Make pages fully responsive (mobile-first approach)\n"
|
|
63
|
+
"- Include smooth transitions and subtle animations\n"
|
|
64
|
+
"- Add proper meta tags, viewport settings, and favicon links\n"
|
|
65
|
+
"- Use Google Fonts via CDN link\n"
|
|
66
|
+
"- Write accessible markup (ARIA labels, alt text, focus styles)\n\n"
|
|
67
|
+
"Generate complete, self-contained files. Every HTML page should be fully functional "
|
|
68
|
+
"when opened directly in a browser.\n\n"
|
|
69
|
+
"IMPORTANT: Output ONLY the fenced code blocks with complete file contents. "
|
|
70
|
+
"Do not include any other text or explanation."
|
|
71
|
+
),
|
|
72
|
+
name="developer",
|
|
73
|
+
description="Generates HTML/CSS/JS files for each page (plain text mode)",
|
|
74
|
+
output_key="page_output",
|
|
75
|
+
)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Project Manager agent factory."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from prompture import Agent
|
|
6
|
+
|
|
7
|
+
from ..models import SitePlan
|
|
8
|
+
from .personas import PM_PERSONA
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def create_pm_agent(model: str) -> Agent:
|
|
12
|
+
"""Create the PM agent that produces a SitePlan."""
|
|
13
|
+
return Agent(
|
|
14
|
+
model,
|
|
15
|
+
system_prompt=PM_PERSONA,
|
|
16
|
+
output_type=SitePlan,
|
|
17
|
+
name="pm",
|
|
18
|
+
description="Plans website structure and pages",
|
|
19
|
+
output_key="site_plan",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_pm_agent_plain(model: str) -> Agent:
|
|
24
|
+
"""Create a PM agent WITHOUT output_type for models that don't support structured output.
|
|
25
|
+
|
|
26
|
+
Uses explicit JSON instructions in the system prompt instead of schema enforcement.
|
|
27
|
+
The caller is responsible for parsing the JSON output manually.
|
|
28
|
+
"""
|
|
29
|
+
json_schema = SitePlan.model_json_schema()
|
|
30
|
+
return Agent(
|
|
31
|
+
model,
|
|
32
|
+
system_prompt=(
|
|
33
|
+
PM_PERSONA.system_prompt
|
|
34
|
+
+ "\n\nIMPORTANT: You MUST respond with ONLY a valid JSON object matching this schema:\n"
|
|
35
|
+
+ f"```json\n{__import__('json').dumps(json_schema, indent=2)}\n```\n"
|
|
36
|
+
"Do NOT include any text before or after the JSON. Return ONLY the JSON object."
|
|
37
|
+
),
|
|
38
|
+
name="pm",
|
|
39
|
+
description="Plans website structure and pages (plain text mode)",
|
|
40
|
+
output_key="site_plan",
|
|
41
|
+
)
|
|
@@ -11,7 +11,7 @@ from typing import Any
|
|
|
11
11
|
|
|
12
12
|
from prompture import GroupCallbacks, GroupResult, SequentialGroup
|
|
13
13
|
|
|
14
|
-
from ..agents.orchestrator import create_dynamic_pipeline
|
|
14
|
+
from ..agents.orchestrator import _agent_model, create_dynamic_pipeline
|
|
15
15
|
from ..config import settings
|
|
16
16
|
from ..models import AgentConfig, AgentRun, PageOutput, Project, SitePlan, StyleSpec, WSEvent
|
|
17
17
|
from .gemini_patch import apply_gemini_patch
|
|
@@ -106,6 +106,7 @@ class GenerationPipeline:
|
|
|
106
106
|
self._run_start_times: dict[str, float] = {}
|
|
107
107
|
self._developer_output_text: str = "" # direct capture from callback
|
|
108
108
|
self._developer_tool_calls: list[dict] = [] # tool calls from developer agent
|
|
109
|
+
self._agent_models: dict[str, str] = {} # agent_key -> resolved model name
|
|
109
110
|
|
|
110
111
|
def _emit(self, event_type: str, agent: str = "", data: dict[str, Any] | None = None) -> None:
|
|
111
112
|
"""Fire a WebSocket event if a callback is registered."""
|
|
@@ -146,7 +147,8 @@ class GenerationPipeline:
|
|
|
146
147
|
def _on_agent_start(name: str, prompt: str) -> None:
|
|
147
148
|
agent_key = _agent_name_to_key(name)
|
|
148
149
|
started_at = datetime.now(timezone.utc).isoformat()
|
|
149
|
-
self.
|
|
150
|
+
agent_model = self._agent_models.get(agent_key, "")
|
|
151
|
+
self._emit("agent_start", agent=agent_key, data={"started_at": started_at, "model": agent_model})
|
|
150
152
|
run = AgentRun(
|
|
151
153
|
project_id=project.id,
|
|
152
154
|
page_slug=slug,
|
|
@@ -207,6 +209,7 @@ class GenerationPipeline:
|
|
|
207
209
|
", ".join(f"{k}=...({len(str(v))})" for k, v in (tc.get("arguments") or {}).items()),
|
|
208
210
|
)
|
|
209
211
|
|
|
212
|
+
agent_model = self._agent_models.get(agent_key, "")
|
|
210
213
|
self._emit(
|
|
211
214
|
"agent_complete",
|
|
212
215
|
agent=agent_key,
|
|
@@ -216,6 +219,7 @@ class GenerationPipeline:
|
|
|
216
219
|
"input_tokens": input_tokens,
|
|
217
220
|
"output_tokens": output_tokens,
|
|
218
221
|
"tool_calls_count": len(tool_calls),
|
|
222
|
+
"model": agent_model,
|
|
219
223
|
},
|
|
220
224
|
)
|
|
221
225
|
|
|
@@ -249,11 +253,14 @@ class GenerationPipeline:
|
|
|
249
253
|
self._emit("phase_start", data={"phase": "planning", "slug": slug, "version": version_number})
|
|
250
254
|
|
|
251
255
|
try:
|
|
252
|
-
# ---
|
|
253
|
-
from ..agents.orchestrator import _agent_model
|
|
256
|
+
# --- Resolve model for each agent and store for WS events ---
|
|
254
257
|
from ..agents.pm import create_pm_agent
|
|
255
258
|
|
|
256
|
-
|
|
259
|
+
for agent_key in ("pm", "designer", "developer", "reviewer"):
|
|
260
|
+
self._agent_models[agent_key] = _agent_model(agent_key, model, self._agent_configs)
|
|
261
|
+
|
|
262
|
+
# --- Phase A: Run PM agent standalone to get SitePlan ---
|
|
263
|
+
pm_model = self._agent_models["pm"]
|
|
257
264
|
pm_agent = create_pm_agent(pm_model)
|
|
258
265
|
|
|
259
266
|
pm_callbacks = GroupCallbacks(
|
|
@@ -268,8 +275,26 @@ class GenerationPipeline:
|
|
|
268
275
|
)
|
|
269
276
|
_patch_pipeline_deps(pm_pipeline, deps)
|
|
270
277
|
|
|
271
|
-
|
|
272
|
-
|
|
278
|
+
try:
|
|
279
|
+
pm_result = pm_pipeline.run(page_prompt)
|
|
280
|
+
site_plan_text = pm_result.shared_state.get("site_plan", "")
|
|
281
|
+
except Exception as pm_exc:
|
|
282
|
+
# Fallback: retry PM without structured output (for models like Kimi K2.5)
|
|
283
|
+
logger.warning(
|
|
284
|
+
"PM agent failed with structured output, retrying in plain text mode: %s",
|
|
285
|
+
pm_exc,
|
|
286
|
+
)
|
|
287
|
+
from ..agents.pm import create_pm_agent_plain
|
|
288
|
+
|
|
289
|
+
pm_agent_plain = create_pm_agent_plain(pm_model)
|
|
290
|
+
pm_pipeline_plain = SequentialGroup(
|
|
291
|
+
[(pm_agent_plain, "{prompt}")],
|
|
292
|
+
callbacks=pm_callbacks,
|
|
293
|
+
state={"prompt": page_prompt},
|
|
294
|
+
)
|
|
295
|
+
_patch_pipeline_deps(pm_pipeline_plain, deps)
|
|
296
|
+
pm_result = pm_pipeline_plain.run(page_prompt)
|
|
297
|
+
site_plan_text = pm_result.shared_state.get("site_plan", "")
|
|
273
298
|
|
|
274
299
|
# Parse required_agents from the PM output
|
|
275
300
|
required_agents = ["designer", "developer", "reviewer"] # default
|
|
@@ -289,8 +314,7 @@ class GenerationPipeline:
|
|
|
289
314
|
all_agents = ["pm"] + [a for a in required_agents]
|
|
290
315
|
self._emit("pipeline_plan", data={"required_agents": all_agents})
|
|
291
316
|
|
|
292
|
-
# --- Phase B:
|
|
293
|
-
# If designer is skipped, inject a default style_spec
|
|
317
|
+
# --- Phase B: Run Designer standalone (with fallback) if needed ---
|
|
294
318
|
initial_state = {
|
|
295
319
|
"prompt": page_prompt,
|
|
296
320
|
"site_plan": site_plan_text,
|
|
@@ -299,15 +323,76 @@ class GenerationPipeline:
|
|
|
299
323
|
"logo_url": project.logo_url or "",
|
|
300
324
|
"icon_url": project.icon_url or "",
|
|
301
325
|
}
|
|
302
|
-
|
|
326
|
+
|
|
327
|
+
if "designer" in required_agents:
|
|
328
|
+
from prompture import clean_json_text
|
|
329
|
+
|
|
330
|
+
from ..agents.designer import create_designer_agent, create_designer_agent_plain
|
|
331
|
+
|
|
332
|
+
designer_model = self._agent_models["designer"]
|
|
333
|
+
designer_agent = create_designer_agent(designer_model)
|
|
334
|
+
designer_prompt = (
|
|
335
|
+
"Design a visual style for this website:\n\n"
|
|
336
|
+
f"Site Plan: {site_plan_text}\n\n"
|
|
337
|
+
f"Logo URL: {project.logo_url or ''}\n"
|
|
338
|
+
f"Icon URL: {project.icon_url or ''}\n\n"
|
|
339
|
+
"Create a cohesive color scheme, typography, and spacing system."
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
designer_callbacks = GroupCallbacks(
|
|
343
|
+
on_agent_start=_on_agent_start,
|
|
344
|
+
on_agent_complete=_on_agent_complete,
|
|
345
|
+
on_agent_error=_on_agent_error,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
try:
|
|
349
|
+
designer_pipeline = SequentialGroup(
|
|
350
|
+
[(designer_agent, "{designer_prompt}")],
|
|
351
|
+
callbacks=designer_callbacks,
|
|
352
|
+
state={"designer_prompt": designer_prompt},
|
|
353
|
+
)
|
|
354
|
+
_patch_pipeline_deps(designer_pipeline, deps)
|
|
355
|
+
designer_result = designer_pipeline.run(designer_prompt)
|
|
356
|
+
style_spec_text = designer_result.shared_state.get("style_spec", "")
|
|
357
|
+
except Exception as designer_exc:
|
|
358
|
+
# Fallback: retry Designer without structured output
|
|
359
|
+
logger.warning(
|
|
360
|
+
"Designer agent failed with structured output, retrying in plain text mode: %s",
|
|
361
|
+
designer_exc,
|
|
362
|
+
)
|
|
363
|
+
designer_agent_plain = create_designer_agent_plain(designer_model)
|
|
364
|
+
designer_pipeline_plain = SequentialGroup(
|
|
365
|
+
[(designer_agent_plain, "{designer_prompt}")],
|
|
366
|
+
callbacks=designer_callbacks,
|
|
367
|
+
state={"designer_prompt": designer_prompt},
|
|
368
|
+
)
|
|
369
|
+
_patch_pipeline_deps(designer_pipeline_plain, deps)
|
|
370
|
+
designer_result = designer_pipeline_plain.run(designer_prompt)
|
|
371
|
+
style_spec_text = designer_result.shared_state.get("style_spec", "")
|
|
372
|
+
|
|
373
|
+
initial_state["style_spec"] = style_spec_text
|
|
374
|
+
|
|
375
|
+
# Merge designer usage into pm_result for later aggregation
|
|
376
|
+
if hasattr(designer_result, 'aggregate_usage'):
|
|
377
|
+
if not hasattr(pm_result, 'aggregate_usage'):
|
|
378
|
+
pm_result.aggregate_usage = {}
|
|
379
|
+
for k, v in designer_result.aggregate_usage.items():
|
|
380
|
+
if isinstance(v, (int, float)):
|
|
381
|
+
pm_result.aggregate_usage[k] = pm_result.aggregate_usage.get(k, 0) + v
|
|
382
|
+
|
|
383
|
+
# Remove designer from remaining pipeline since we ran it here
|
|
384
|
+
remaining_agents = [a for a in required_agents if a != "designer"]
|
|
385
|
+
else:
|
|
386
|
+
remaining_agents = list(required_agents)
|
|
303
387
|
# Use project's existing style_spec or sensible defaults
|
|
304
388
|
if project.style_spec:
|
|
305
389
|
initial_state["style_spec"] = project.style_spec.model_dump_json()
|
|
306
390
|
else:
|
|
307
391
|
initial_state["style_spec"] = StyleSpec().model_dump_json()
|
|
308
392
|
|
|
393
|
+
# --- Phase C: Build dynamic pipeline for developer+reviewer ---
|
|
309
394
|
remaining_pipeline = create_dynamic_pipeline(
|
|
310
|
-
|
|
395
|
+
remaining_agents,
|
|
311
396
|
model,
|
|
312
397
|
callbacks=group_callbacks,
|
|
313
398
|
agent_configs=self._agent_configs,
|
|
@@ -324,6 +409,68 @@ class GenerationPipeline:
|
|
|
324
409
|
# Merge nested group state back so we can access page_output
|
|
325
410
|
_merge_nested_group_state(remaining_pipeline)
|
|
326
411
|
|
|
412
|
+
# Detect developer failure: Prompture's SequentialGroup catches
|
|
413
|
+
# agent errors internally (doesn't re-raise), so check for a
|
|
414
|
+
# failed result with no files and no developer output.
|
|
415
|
+
_dev_failed = (
|
|
416
|
+
not result.success
|
|
417
|
+
or (
|
|
418
|
+
not written_files
|
|
419
|
+
and not self._developer_output_text
|
|
420
|
+
and not self._developer_tool_calls
|
|
421
|
+
)
|
|
422
|
+
)
|
|
423
|
+
if _dev_failed and not written_files:
|
|
424
|
+
dev_errors = [
|
|
425
|
+
e for e in getattr(result, "errors", [])
|
|
426
|
+
if getattr(e, "agent_name", "") in ("developer", "agentsite_developer")
|
|
427
|
+
]
|
|
428
|
+
logger.warning(
|
|
429
|
+
"Developer pipeline produced no output (success=%s, errors=%d, "
|
|
430
|
+
"written_files=%d, dev_output_len=%d, tool_calls=%d). "
|
|
431
|
+
"Retrying with plain text developer.",
|
|
432
|
+
result.success,
|
|
433
|
+
len(dev_errors),
|
|
434
|
+
len(written_files),
|
|
435
|
+
len(self._developer_output_text),
|
|
436
|
+
len(self._developer_tool_calls),
|
|
437
|
+
)
|
|
438
|
+
from ..agents.developer import create_developer_agent_plain
|
|
439
|
+
|
|
440
|
+
dev_model = self._agent_models["developer"]
|
|
441
|
+
dev_agent_plain = create_developer_agent_plain(dev_model)
|
|
442
|
+
|
|
443
|
+
dev_prompt = (
|
|
444
|
+
"Build the website page based on this plan:\n\n"
|
|
445
|
+
f"Site Plan: {initial_state['site_plan']}\n\n"
|
|
446
|
+
f"Style Spec: {initial_state.get('style_spec', '')}\n\n"
|
|
447
|
+
f"Logo URL: {initial_state.get('logo_url', '')}\n"
|
|
448
|
+
f"Icon URL: {initial_state.get('icon_url', '')}\n\n"
|
|
449
|
+
"Generate complete, self-contained HTML with inline or linked CSS/JS."
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
dev_callbacks = GroupCallbacks(
|
|
453
|
+
on_agent_start=_on_agent_start,
|
|
454
|
+
on_agent_complete=_on_agent_complete,
|
|
455
|
+
on_agent_error=_on_agent_error,
|
|
456
|
+
)
|
|
457
|
+
dev_pipeline_plain = SequentialGroup(
|
|
458
|
+
[(dev_agent_plain, "{dev_prompt}")],
|
|
459
|
+
callbacks=dev_callbacks,
|
|
460
|
+
state={"dev_prompt": dev_prompt},
|
|
461
|
+
)
|
|
462
|
+
_patch_pipeline_deps(dev_pipeline_plain, deps)
|
|
463
|
+
plain_result = dev_pipeline_plain.run(dev_prompt)
|
|
464
|
+
|
|
465
|
+
# Use the plain result's usage and state
|
|
466
|
+
if hasattr(plain_result, 'aggregate_usage'):
|
|
467
|
+
if not hasattr(result, 'aggregate_usage'):
|
|
468
|
+
result.aggregate_usage = {}
|
|
469
|
+
for k, v in plain_result.aggregate_usage.items():
|
|
470
|
+
if isinstance(v, (int, float)):
|
|
471
|
+
result.aggregate_usage[k] = result.aggregate_usage.get(k, 0) + v
|
|
472
|
+
result = plain_result
|
|
473
|
+
|
|
327
474
|
# Merge usage from both phases
|
|
328
475
|
combined_usage = pm_result.aggregate_usage.copy() if hasattr(pm_result, 'aggregate_usage') else {}
|
|
329
476
|
if hasattr(result, 'aggregate_usage'):
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
"""Designer agent factory."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from prompture import Agent
|
|
6
|
-
|
|
7
|
-
from ..models import StyleSpec
|
|
8
|
-
from .personas import DESIGNER_PERSONA
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def create_designer_agent(model: str) -> Agent:
|
|
12
|
-
"""Create the Designer agent that produces a StyleSpec."""
|
|
13
|
-
return Agent(
|
|
14
|
-
model,
|
|
15
|
-
system_prompt=DESIGNER_PERSONA,
|
|
16
|
-
output_type=StyleSpec,
|
|
17
|
-
name="designer",
|
|
18
|
-
description="Defines visual design system (colors, fonts, spacing)",
|
|
19
|
-
output_key="style_spec",
|
|
20
|
-
)
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
"""Developer agent factory."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from prompture import Agent
|
|
6
|
-
|
|
7
|
-
from .personas import DEVELOPER_PERSONA
|
|
8
|
-
from .tools import list_files, read_file, write_file
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def create_developer_agent(model: str) -> Agent:
|
|
12
|
-
"""Create the Developer agent that generates page files.
|
|
13
|
-
|
|
14
|
-
Note: No ``output_type`` is set because the developer writes files
|
|
15
|
-
via the ``write_file`` tool. Forcing structured-output parsing on the
|
|
16
|
-
final text response causes failures when the LLM returns empty text
|
|
17
|
-
after finishing its tool calls. The pipeline already handles file
|
|
18
|
-
extraction from both tool-written files and raw output text.
|
|
19
|
-
"""
|
|
20
|
-
return Agent(
|
|
21
|
-
model,
|
|
22
|
-
system_prompt=DEVELOPER_PERSONA,
|
|
23
|
-
tools=[write_file, read_file, list_files],
|
|
24
|
-
name="developer",
|
|
25
|
-
description="Generates HTML/CSS/JS files for each page",
|
|
26
|
-
output_key="page_output",
|
|
27
|
-
)
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
"""Project Manager agent factory."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from prompture import Agent
|
|
6
|
-
|
|
7
|
-
from ..models import SitePlan
|
|
8
|
-
from .personas import PM_PERSONA
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def create_pm_agent(model: str) -> Agent:
|
|
12
|
-
"""Create the PM agent that produces a SitePlan."""
|
|
13
|
-
return Agent(
|
|
14
|
-
model,
|
|
15
|
-
system_prompt=PM_PERSONA,
|
|
16
|
-
output_type=SitePlan,
|
|
17
|
-
name="pm",
|
|
18
|
-
description="Plans website structure and pages",
|
|
19
|
-
output_key="site_plan",
|
|
20
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|