Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions agent/api/base_agent_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,14 +235,21 @@ async def emit_intermediate_message(message: str) -> None:
logger.info("Got FAILED status, sending runtime error message")
# Get the actual error from the FSM if available
error_details = "Unknown error"
is_agent_search_failed = False

if self.processor_instance.fsm_app:
error_details = self.processor_instance.fsm_app.maybe_error() or "Unknown error"
if hasattr(self.processor_instance.fsm_app, 'is_agent_search_failed_error'):
is_agent_search_failed = self.processor_instance.fsm_app.is_agent_search_failed_error()

logger.error(f"FSM failed with error: {error_details}")

error_message = f"Error: {error_details}"
if "No solutions found" in error_details:
error_message = "The agent was unable to generate a solution after exhausting all attempts. This usually means the request is too complex or ambiguous. Please try simplifying your request or providing more specific details."
if is_agent_search_failed:
# User-friendly message from AgentSearchFailedException
error_message = error_details
else:
# Other errors - show with context
error_message = f"An error occurred during processing: {error_details}"

runtime_error_message = InternalMessage(
role="assistant",
Expand Down
10 changes: 10 additions & 0 deletions agent/core/actors.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@
logger = get_logger(__name__)


class AgentSearchFailedException(Exception):
"""Exception raised when an agent's search process fails to find candidates."""
def __init__(self, agent_name: str, message: str = "No candidates to evaluate, search terminated"):
self.agent_name = agent_name
self.message = message
# Create a more user-friendly message
user_message = f"The {agent_name} encountered an issue: {message}. This typically happens when the agent reaches its maximum search depth or cannot find valid solutions. Please try refining your request or providing more specific details."
super().__init__(user_message)


@dataclasses.dataclass
class BaseData:
workspace: Workspace
Expand Down
43 changes: 42 additions & 1 deletion agent/core/application.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from typing import Protocol
from typing import Protocol, Any
from dataclasses import dataclass, field
from typing import Optional, Dict


class ApplicationBase(Protocol):
Expand All @@ -9,3 +11,42 @@ def state_output(self) -> dict: ...
@property
def is_completed(self) -> bool: ...
def maybe_error(self) -> str | None: ...
def is_agent_search_failed_error(self) -> bool:
"""Check if the error is an AgentSearchFailedException"""
...


@dataclass
class BaseApplicationContext:
"""Base context class with common fields for all FSM applications"""
user_prompt: str
feedback_data: Optional[str] = None
files: Dict[str, str] = field(default_factory=dict)
error: Optional[str] = None
error_type: Optional[str] = None # Store the exception class name

def dump_base(self) -> dict:
"""Dump base fields to a dictionary"""
return {
"user_prompt": self.user_prompt,
"feedback_data": self.feedback_data,
"files": self.files,
"error": self.error,
"error_type": self.error_type,
}


class BaseFSMApplication:
"""Base class for FSM applications with common functionality"""

def __init__(self, client: Any, fsm: Any):
self.client = client
self.fsm = fsm

def maybe_error(self) -> str | None:
"""Get the error message if any"""
return self.fsm.context.error

def is_agent_search_failed_error(self) -> bool:
"""Check if the error is an AgentSearchFailedException"""
return self.fsm.context.error_type == "AgentSearchFailedException"
472 changes: 423 additions & 49 deletions agent/laravel_agent/actors.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion agent/laravel_agent/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ async def run_final_steps(
llm=llm,
workspace=workspace.clone(),
beam_width=5,
max_depth=100, # Increased to 100 iterations as requested
max_depth=50, # Reduced to 50 iterations as requested
system_prompt=playbooks.APPLICATION_SYSTEM_PROMPT,
# files_allowed will use the default from actors.py
event_callback=event_callback,
Expand Down
171 changes: 169 additions & 2 deletions agent/laravel_agent/playbooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,64 @@
6. **complete** - Mark the task as complete (runs tests and type checks)
- No inputs required

7. **artisan_make** - Generate Laravel boilerplate code using artisan make commands
- Input: type (string), name (string), options (object)
- Supports all Laravel make commands including:
- Basic: controller, model, migration, seeder, factory, request, resource, middleware
- Advanced: livewire, filament components, notifications, jobs, events, policies, etc.
- Example: artisan_make(type="controller", name="TodoController", options={"resource": true})

8. **artisan_make_migration** - Create database migration files
- Input: name (string), create (string, optional), table (string, optional)
- Example: artisan_make_migration(name="create_todos_table", create="todos")

9. **artisan_migrate** - Run database migrations
- Input: fresh (boolean), seed (boolean), force (boolean) - all optional
- Example: artisan_migrate(fresh=true, seed=true)

10. **run_pint** - Format code using Laravel Pint
- Input: path (string, optional), preset (string, optional)
- Automatically fixes code style issues
- Example: run_pint(path="app/Http/Controllers")

11. **run_artisan_command** - Execute any other artisan command
- Input: command (string), arguments (array of strings, optional)
- Example: run_artisan_command(command="cache:clear")

# Tool Usage Guidelines

- Always use tools to create or modify files - do not output file content in your responses
- Use write_file for new files or complete rewrites
- PREFER artisan_make commands over write_file for Laravel components:
- Use artisan_make for: controllers, models, migrations, seeders, factories, etc.
- Use write_file only for: React/Vue components, custom services, config files
- Use edit_file for small, targeted changes to existing files
- Pint formatting is AUTOMATIC after ALL PHP file operations (write_file, edit_file, artisan_make)
- You do NOT need to call run_pint() manually - it runs automatically
- Use artisan_make_migration for database schema changes, not write_file
- Use artisan_migrate to apply migrations after creating them
- Ensure proper indentation when using edit_file - the search string must match exactly
- Code will be linted and type-checked, so ensure correctness
- Use multiple tools in a single step if needed.
- Use multiple tools in a single step if needed
- Run tests and linting BEFORE using complete() to catch errors early
- If tests fail, analyze the specific error message - don't guess at fixes

## COMMON MISTAKES TO AVOID:

1. ❌ WRONG: Using write_file to create a model
✅ RIGHT: artisan_make(type="model", name="Product")

2. ❌ WRONG: Using run_artisan_command(command="make:migration create_posts_table")
✅ RIGHT: artisan_make_migration(name="create_posts_table", create="posts")

3. ❌ WRONG: Creating controller without options
✅ RIGHT: artisan_make(type="controller", name="PostController", options={{"resource": true}})

4. ❌ WRONG: Forgetting to run migrations
✅ RIGHT: Always run artisan_migrate() after creating/editing migrations

5. ❌ WRONG: Manually calling run_pint() after every operation
✅ RIGHT: Pint runs automatically after ALL PHP file operations

## Common edit_file Errors to Avoid:

1. **Using ellipsis (...) in search text**:
Expand All @@ -58,6 +105,83 @@
You are a software engineer specializing in Laravel application development. Strictly follow provided rules. Don't be chatty, keep on solving the problem, not describing what you are doing.
CRITICAL: During refinement requests - if the user provides a clear implementation request (like "add emojis" or "make it more engaging"), IMPLEMENT IT IMMEDIATELY. Do NOT ask follow-up questions. The user wants action, not clarification. Make reasonable assumptions and build working code.

IMPORTANT: Laravel provides extensive artisan make commands. Always use these instead of manually creating files:
- For models, controllers, migrations: Use artisan_make or artisan_make_migration
- For code formatting: Use run_pint after creating/modifying PHP files
- For other artisan commands: Use run_artisan_command

Available artisan make types: controller, model, migration, seeder, factory, request, resource,
middleware, provider, command, event, listener, job, mail, notification, observer, policy, rule,
scope, cast, channel, exception, test, component, view, trait, interface, enum, class, cache-table,
job-middleware, livewire, livewire-form, livewire-table, notifications-table, queue-batches-table,
queue-failed-table, queue-table, session-table, volt, and all Filament-related components.

## CRITICAL WORKFLOW EXAMPLES - FOLLOW THESE PATTERNS:

### Example 1: Creating a Blog Feature
User: "Create a blog with posts"
CORRECT APPROACH:
1. artisan_make(type="model", name="Post", options={{"migration": true, "factory": true}})
2. artisan_make(type="controller", name="PostController", options={{"resource": true, "model": "Post"}})
3. artisan_make(type="request", name="StorePostRequest")
4. artisan_make(type="request", name="UpdatePostRequest")
5. edit_file to update the migration with columns
# NO run_pint() needed - it runs automatically after EVERY step!

### Example 2: Creating a Model with Relations
User: "Create Product model with categories"
CORRECT APPROACH:
1. artisan_make(type="model", name="Category", options={{"migration": true}})
2. artisan_make(type="model", name="Product", options={{"migration": true}})
3. edit_file to add columns to migrations
4. edit_file to add relationships to models
5. artisan_migrate() to run migrations

### Example 3: Creating API Resources
User: "Create API for users"
CORRECT APPROACH:
1. artisan_make(type="controller", name="Api/UserController", options={{"api": true}})
2. artisan_make(type="resource", name="UserResource")
3. artisan_make(type="resource", name="UserCollection")

NEVER manually create these files with write_file - always use artisan commands!

## OPTIMAL TASK SEQUENCES (COPY THESE PATTERNS):

### For "Create a Todo App":
```
1. artisan_make(type="model", name="Todo", options={{"migration": true, "factory": true}})
2. edit_file on migration to add: title(string), description(text nullable), completed(boolean default false)
3. artisan_make(type="controller", name="TodoController", options={{"resource": true, "model": "Todo"}})
4. artisan_make(type="request", name="StoreTodoRequest")
5. artisan_make(type="request", name="UpdateTodoRequest")
6. artisan_migrate()
7. edit_file on routes/web.php to add: Route::resource('todos', TodoController::class)
# Pint runs automatically after EVERY PHP file operation - no manual calls needed!
```

### For "Create Blog with Categories":
```
1. artisan_make(type="model", name="Category", options={{"migration": true}})
2. artisan_make(type="model", name="Post", options={{"migration": true, "factory": true}})
3. edit_file on create_categories_table migration
4. edit_file on create_posts_table migration (add foreign key)
5. artisan_make(type="controller", name="PostController", options={{"resource": true}})
6. artisan_make(type="controller", name="CategoryController", options={{"resource": true}})
7. artisan_migrate()
# Pint runs automatically - no manual call needed!
```

### For "Create User Authentication":
```
1. artisan_make(type="controller", name="Auth/LoginController")
2. artisan_make(type="controller", name="Auth/RegisterController")
3. artisan_make(type="request", name="LoginRequest")
4. artisan_make(type="request", name="RegisterRequest")
5. edit_file on routes/web.php for auth routes
# Pint runs automatically - no manual call needed!
```

{TOOL_USAGE_RULES}

# File Structure and Allowed Paths
Expand Down Expand Up @@ -127,6 +251,14 @@

# Laravel Migration Guidelines - COMPLETE WORKING EXAMPLE

IMPORTANT: Always use artisan_make_migration first, then edit_file to add columns:

### Step 1: Create migration with artisan
```
artisan_make_migration(name="create_posts_table", create="posts")
```

### Step 2: Edit the migration to add columns
When creating Laravel migrations, use EXACTLY this pattern (copy-paste and modify):

```php
Expand Down Expand Up @@ -163,6 +295,41 @@
}};
```

## COMMON MIGRATION PATTERNS - USE THESE:

### For a TODO/TASK table:
```php
Schema::create('todos', function (Blueprint $table) {{
$table->id();
$table->string('title');
$table->text('description')->nullable();
$table->boolean('completed')->default(false);
$table->integer('priority')->default(0);
$table->timestamp('due_date')->nullable();
$table->timestamps();
}});
```

### For a BLOG POST table:
```php
Schema::create('posts', function (Blueprint $table) {{
$table->id();
$table->string('title');
$table->string('slug')->unique();
$table->text('content');
$table->text('excerpt')->nullable();
$table->boolean('is_published')->default(false);
$table->timestamp('published_at')->nullable();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('category_id')->nullable()->constrained();
$table->string('featured_image')->nullable();
$table->timestamps();

$table->index(['is_published', 'published_at']);
$table->index('slug');
}});
```

For a more complex example (e.g., customers table for CRM):
```php
<?php
Expand Down
19 changes: 15 additions & 4 deletions agent/nicegui_agent/actors.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from typing import Callable, Awaitable
from core.base_node import Node
from core.workspace import Workspace
from core.actors import BaseData, FileOperationsActor
from core.actors import BaseData, FileOperationsActor, AgentSearchFailedException
from llm.common import AsyncLLM, Message, TextRaw, Tool, ToolUse, ToolUseResult
from nicegui_agent import playbooks
from core.notification_utils import notify_if_callback, notify_stage
Expand Down Expand Up @@ -96,8 +96,16 @@ async def execute(
iteration += 1
candidates = self.select(self.root)
if not candidates:
logger.info("No candidates to evaluate, search terminated")
break
logger.error("No candidates to evaluate, search terminated")
await notify_stage(
self.event_callback,
"❌ NiceGUI agent failed: No candidates to evaluate",
"failed"
)
raise AgentSearchFailedException(
agent_name="NiceguiActor",
message="No candidates to evaluate, search terminated"
)

await notify_if_callback(
self.event_callback,
Expand Down Expand Up @@ -134,7 +142,10 @@ async def execute(
"❌ NiceGUI application generation failed",
"failed",
)
raise ValueError("No solutions found")
raise AgentSearchFailedException(
agent_name="NiceguiActor",
message="Failed to find a solution after all iterations"
)
return solution

def select(self, node: Node[BaseData]) -> list[Node[BaseData]]:
Expand Down
Loading