Planner: respuesta directa para saludos y preguntas simples
El planner ahora puede devolver direct_response en vez de un plan cuando el mensaje no requiere herramientas (saludos, preguntas generales, conversación casual). - planner.py: prompt actualizado con formato direct_response - engine.py: si planner devuelve string, emitir como texto y completar sin ejecutar steps "hola" → "¡Hola! ¿En qué puedo ayudarte hoy?" (0 steps, 0 tools) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,16 +12,21 @@ from .base import BaseAgent
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
PLANNER_SYSTEM_PROMPT = """Eres un Agente Planificador. Tu rol es descomponer un objetivo en un plan de ejecución estructurado.
|
||||
PLANNER_SYSTEM_PROMPT = """Eres un Agente Planificador de Acai CMS. Tu rol es analizar el mensaje del usuario y decidir cómo responder.
|
||||
|
||||
## Instrucciones
|
||||
- Analiza el objetivo y divídelo en pasos concretos y ordenados.
|
||||
- Cada paso debe ser ejecutable de forma independiente por un agente especializado.
|
||||
- Asigna cada paso al rol de agente apropiado: coder, collector o reviewer.
|
||||
- Si el mensaje es un saludo, pregunta general, conversación casual o no requiere herramientas → devuelve una respuesta directa.
|
||||
- Si el mensaje requiere acción (crear módulos, editar contenido, explorar web, consultar datos) → genera un plan de ejecución.
|
||||
- Responde SIEMPRE en español.
|
||||
|
||||
## Formato de salida
|
||||
Devuelve SOLO un objeto JSON:
|
||||
|
||||
### Para respuestas directas (saludos, preguntas simples):
|
||||
{
|
||||
"direct_response": "Tu respuesta aquí. Sé amable y conciso."
|
||||
}
|
||||
|
||||
### Para tareas que requieren herramientas:
|
||||
{
|
||||
"plan": [
|
||||
{"description": "descripción del paso", "agent_role": "coder|collector|reviewer"},
|
||||
@@ -31,7 +36,7 @@ Devuelve SOLO un objeto JSON:
|
||||
"facts": ["hechos establecidos del análisis"]
|
||||
}
|
||||
|
||||
NO incluyas comentarios fuera del JSON."""
|
||||
Devuelve SOLO el objeto JSON, sin comentarios fuera."""
|
||||
|
||||
|
||||
def create_planner_profile() -> AgentProfile:
|
||||
@@ -55,26 +60,34 @@ def create_planner_profile() -> AgentProfile:
|
||||
class PlannerAgent(BaseAgent):
|
||||
"""Generates execution plans from objectives."""
|
||||
|
||||
async def plan(self, session: SessionState) -> tuple[list[TaskStep], dict[str, int]]:
|
||||
"""Generate a plan and return (TaskSteps, usage)."""
|
||||
async def plan(self, session: SessionState) -> tuple[list[TaskStep] | str, dict[str, int]]:
|
||||
"""Generate a plan or a direct response.
|
||||
|
||||
Returns:
|
||||
(steps, usage) if plan needed
|
||||
(direct_response_string, usage) if no plan needed
|
||||
"""
|
||||
result = await self.execute(session, max_steps=1)
|
||||
usage = result.get("usage", {"input_tokens": 0, "output_tokens": 0})
|
||||
content = result["content"].strip()
|
||||
|
||||
# Parse the JSON plan from the model output
|
||||
# Parse the JSON from the model output
|
||||
try:
|
||||
# Try to extract JSON from the content
|
||||
json_str = content
|
||||
if "```" in content:
|
||||
# Extract from code block
|
||||
start = content.find("{")
|
||||
end = content.rfind("}") + 1
|
||||
if start >= 0 and end > start:
|
||||
json_str = content[start:end]
|
||||
|
||||
parsed = json.loads(json_str)
|
||||
steps: list[TaskStep] = []
|
||||
|
||||
# Check for direct response (no plan needed)
|
||||
if "direct_response" in parsed:
|
||||
return parsed["direct_response"], usage
|
||||
|
||||
# Build plan steps
|
||||
steps: list[TaskStep] = []
|
||||
for item in parsed.get("plan", []):
|
||||
steps.append(
|
||||
TaskStep(
|
||||
@@ -84,7 +97,6 @@ class PlannerAgent(BaseAgent):
|
||||
)
|
||||
)
|
||||
|
||||
# Extract constraints and facts into task state
|
||||
if session.current_task:
|
||||
session.current_task.constraints.extend(
|
||||
parsed.get("constraints", [])
|
||||
@@ -97,7 +109,6 @@ class PlannerAgent(BaseAgent):
|
||||
|
||||
except (json.JSONDecodeError, KeyError) as e:
|
||||
logger.warning("Failed to parse planner output: %s", e)
|
||||
# Fallback: single step with the full objective
|
||||
return [
|
||||
TaskStep(
|
||||
description=session.current_task.objective
|
||||
|
||||
@@ -118,8 +118,54 @@ class OrchestratorEngine:
|
||||
planner_usage: dict[str, int] = {"input_tokens": 0, "output_tokens": 0}
|
||||
try:
|
||||
planner = self._create_agent(AgentRole.PLANNER)
|
||||
plan_steps, planner_usage = await planner.plan(session)
|
||||
task.plan = plan_steps
|
||||
plan_result, planner_usage = await planner.plan(session)
|
||||
|
||||
# Direct response — no plan needed (saludo, pregunta simple)
|
||||
if isinstance(plan_result, str):
|
||||
logger.info("Planner returned direct response for task %s", task.task_id)
|
||||
task.status = TaskStatus.COMPLETED
|
||||
session.complete_task()
|
||||
|
||||
# Emit as text streaming for the frontend
|
||||
await self.sse.emit(
|
||||
EventType.AGENT_DELTA,
|
||||
{"agent": "planner", "delta": plan_result, "step": 0},
|
||||
session_id=session.session_id,
|
||||
)
|
||||
|
||||
cost_usd = (
|
||||
(planner_usage.get("input_tokens", 0) / 1_000_000) * settings.cost_per_1m_input
|
||||
+ (planner_usage.get("output_tokens", 0) / 1_000_000) * settings.cost_per_1m_output
|
||||
)
|
||||
|
||||
await self.sse.emit(
|
||||
EventType.EXECUTION_COMPLETED,
|
||||
{
|
||||
"session_id": session.session_id,
|
||||
"task_id": task.task_id,
|
||||
"steps_completed": 0,
|
||||
"steps_failed": [],
|
||||
"status": "completed",
|
||||
"usage": planner_usage,
|
||||
"total_cost_usd": round(cost_usd, 6),
|
||||
},
|
||||
session_id=session.session_id,
|
||||
)
|
||||
|
||||
return {
|
||||
"session_id": session.session_id,
|
||||
"task_id": task.task_id,
|
||||
"content": plan_result,
|
||||
"steps_completed": 0,
|
||||
"steps_failed": [],
|
||||
"artifacts_count": 0,
|
||||
"review": "",
|
||||
"status": "completed",
|
||||
"usage": planner_usage,
|
||||
"total_cost_usd": round(cost_usd, 6),
|
||||
}
|
||||
|
||||
task.plan = plan_result
|
||||
task.status = TaskStatus.EXECUTING
|
||||
except Exception as e:
|
||||
logger.error("Planning failed: %s", e)
|
||||
@@ -134,7 +180,7 @@ class OrchestratorEngine:
|
||||
|
||||
logger.info(
|
||||
"Plan created with %d steps for task %s",
|
||||
len(plan_steps),
|
||||
len(plan_result),
|
||||
task.task_id,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user