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__)
|
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
|
## Instrucciones
|
||||||
- Analiza el objetivo y divídelo en pasos concretos y ordenados.
|
- Si el mensaje es un saludo, pregunta general, conversación casual o no requiere herramientas → devuelve una respuesta directa.
|
||||||
- Cada paso debe ser ejecutable de forma independiente por un agente especializado.
|
- Si el mensaje requiere acción (crear módulos, editar contenido, explorar web, consultar datos) → genera un plan de ejecución.
|
||||||
- Asigna cada paso al rol de agente apropiado: coder, collector o reviewer.
|
|
||||||
- Responde SIEMPRE en español.
|
- Responde SIEMPRE en español.
|
||||||
|
|
||||||
## Formato de salida
|
## 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": [
|
"plan": [
|
||||||
{"description": "descripción del paso", "agent_role": "coder|collector|reviewer"},
|
{"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"]
|
"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:
|
def create_planner_profile() -> AgentProfile:
|
||||||
@@ -55,26 +60,34 @@ def create_planner_profile() -> AgentProfile:
|
|||||||
class PlannerAgent(BaseAgent):
|
class PlannerAgent(BaseAgent):
|
||||||
"""Generates execution plans from objectives."""
|
"""Generates execution plans from objectives."""
|
||||||
|
|
||||||
async def plan(self, session: SessionState) -> tuple[list[TaskStep], dict[str, int]]:
|
async def plan(self, session: SessionState) -> tuple[list[TaskStep] | str, dict[str, int]]:
|
||||||
"""Generate a plan and return (TaskSteps, usage)."""
|
"""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)
|
result = await self.execute(session, max_steps=1)
|
||||||
usage = result.get("usage", {"input_tokens": 0, "output_tokens": 0})
|
usage = result.get("usage", {"input_tokens": 0, "output_tokens": 0})
|
||||||
content = result["content"].strip()
|
content = result["content"].strip()
|
||||||
|
|
||||||
# Parse the JSON plan from the model output
|
# Parse the JSON from the model output
|
||||||
try:
|
try:
|
||||||
# Try to extract JSON from the content
|
|
||||||
json_str = content
|
json_str = content
|
||||||
if "```" in content:
|
if "```" in content:
|
||||||
# Extract from code block
|
|
||||||
start = content.find("{")
|
start = content.find("{")
|
||||||
end = content.rfind("}") + 1
|
end = content.rfind("}") + 1
|
||||||
if start >= 0 and end > start:
|
if start >= 0 and end > start:
|
||||||
json_str = content[start:end]
|
json_str = content[start:end]
|
||||||
|
|
||||||
parsed = json.loads(json_str)
|
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", []):
|
for item in parsed.get("plan", []):
|
||||||
steps.append(
|
steps.append(
|
||||||
TaskStep(
|
TaskStep(
|
||||||
@@ -84,7 +97,6 @@ class PlannerAgent(BaseAgent):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Extract constraints and facts into task state
|
|
||||||
if session.current_task:
|
if session.current_task:
|
||||||
session.current_task.constraints.extend(
|
session.current_task.constraints.extend(
|
||||||
parsed.get("constraints", [])
|
parsed.get("constraints", [])
|
||||||
@@ -97,7 +109,6 @@ class PlannerAgent(BaseAgent):
|
|||||||
|
|
||||||
except (json.JSONDecodeError, KeyError) as e:
|
except (json.JSONDecodeError, KeyError) as e:
|
||||||
logger.warning("Failed to parse planner output: %s", e)
|
logger.warning("Failed to parse planner output: %s", e)
|
||||||
# Fallback: single step with the full objective
|
|
||||||
return [
|
return [
|
||||||
TaskStep(
|
TaskStep(
|
||||||
description=session.current_task.objective
|
description=session.current_task.objective
|
||||||
|
|||||||
@@ -118,8 +118,54 @@ class OrchestratorEngine:
|
|||||||
planner_usage: dict[str, int] = {"input_tokens": 0, "output_tokens": 0}
|
planner_usage: dict[str, int] = {"input_tokens": 0, "output_tokens": 0}
|
||||||
try:
|
try:
|
||||||
planner = self._create_agent(AgentRole.PLANNER)
|
planner = self._create_agent(AgentRole.PLANNER)
|
||||||
plan_steps, planner_usage = await planner.plan(session)
|
plan_result, planner_usage = await planner.plan(session)
|
||||||
task.plan = plan_steps
|
|
||||||
|
# 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
|
task.status = TaskStatus.EXECUTING
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Planning failed: %s", e)
|
logger.error("Planning failed: %s", e)
|
||||||
@@ -134,7 +180,7 @@ class OrchestratorEngine:
|
|||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"Plan created with %d steps for task %s",
|
"Plan created with %d steps for task %s",
|
||||||
len(plan_steps),
|
len(plan_result),
|
||||||
task.task_id,
|
task.task_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user