Implementación de razonamiento y planificación en Python
Esta sección presenta la capacidad de inteligencia central de los Agentes de IA, es decir, la capacidad de razonamiento y planificación.
A diferencia del modo tradicional de ejecución de instrucciones, un Agente con capacidad de razonamiento puede llevar a cabo procesos de pensamiento complejos.
Es capaz de descomponer tareas complejas en pasos ejecutables y tomar decisiones basándose en el contexto.
framework ReAct
ReAct (Reasoning + Acting) es un marco que combina el razonamiento con la acción.
En el modo ReAct, el Agente alterna entre razonamiento y acción, formando un ciclo cerrado.
El razonamiento guía las acciones, y los resultados de estas retroalimentan el proceso de razonamiento, en un ciclo continuo.
ReAct simula el proceso humano de resolución de problemas: pensar antes de actuar, observar los resultados después de actuar y luego continuar razonando con base en la nueva información.
Principio de funcionamiento
La idea central de ReAct puede resumirse como un ciclo de tres fases:
Primer paso, fase de razonamiento. El Agente genera el pensamiento de acción para el siguiente paso basándose en el contexto actual.
Segundo paso, fase de acción. El Agente ejecuta la herramienta o acción seleccionada.
Tercer paso, fase de observación. El agente recibe el resultado de la acción, actualiza el contexto y regresa al primer paso para continuar el razonamiento.
Implementación de código
A continuación se muestra una implementación simplificada de un Agente ReAct:
Implementación básica del agente ReAct
"""
Implementación del Agent en el marco ReAct
Mecanismo central: razonamiento -> acción -> observación -> razonamiento (bucle)
"""
def __init__(self, llm, tools, max_iterations=5):
# Instancia de LLM, utilizada para inferencia y generación
self.llm = llm
# Lista de herramientas disponibles
self.tools = tools
# Número máximo de iteraciones para evitar bucles infinitos
self.max_iterations = max_iterations
def run(self, task):
"""
Ejecutar el bucle ReAct
:param task: Descripción de la tarea del usuario.
:return: Resultado de la ejecución
"""
# Inicializar el contexto, incluyendo la tarea y la información histórica
context = {
"task": task,
"steps": [], # Registro de pasos ejecutados
"observations": [] # Registro de observaciones
}
for i in range(self.max_iterations):
# Etapa 1: Razonamiento - Generar la siguiente acción basada en el contexto actual
reasoning = self.llm.reason(context)
# Determinar si es necesario ejecutar la herramienta o devolver la respuesta directamente
if reasoning.needs_action:
# Seleccionar la herramienta a utilizar
action = reasoning.select_tool(self.tools)
# Ejecutar la herramienta y obtener las observaciones
observation = action.execute(reasoning.tool_input)
# Agregar las observaciones al contexto
context["observations"].append(observation)
else:
# No se necesita acción, devolver directamente la respuesta obtenida por razonamiento.
return reasoning.answer
# Se alcanzó el número máximo de iteraciones sin completar
return Se alcanzó el límite máximo de iteraciones
Escenarios de aplicación típicos
El modo ReAct es especialmente adecuado para los siguientes escenarios:
Tareas que requieren explorar el entorno y recopilar información antes de poder completarse.
Escenario de búsqueda inteligente: el agente necesita primero buscar información y luego razonar y resumir con base en los resultados de la búsqueda.
Preguntas y respuestas conversacionales: el Agente necesita aclarar requisitos y obtener información mediante diálogos de múltiples turnos.
Resolución de problemas complejos: el problema no puede resolverse en un solo paso y requiere múltiples pasos intermedios.
Nota: la ventaja del modo ReAct radica en su flexibilidad, ya que puede ajustar dinámicamente las acciones posteriores según el resultado de cada paso. Pero esto también significa que la ruta de ejecución puede ser inestable, y resulta adecuado para tareas que requieren exploración.
cadena de pensamiento (Chain of Thought, CoT)
La cadena de pensamiento es una técnica que impulsa al modelo a mostrar el proceso de razonamiento paso a paso.
CoT no hace que el modelo dé la respuesta directamente, sino que lo guía para que primero muestre los pasos de razonamiento y luego llegue a la conclusión final.
¿Por qué es necesaria la cadena de pensamiento?
Hacer que el modelo genere la respuesta directamente presenta varios problemas:
Los pasos intermedios del razonamiento complejo tienden a omitirse o saltarse fácilmente.
Es difícil localizar dónde ocurrió el error.
El usuario no puede comprender el proceso de pensamiento del modelo.
La cadena de pensamiento resuelve estos problemas al exigir que el modelo muestre el proceso de razonamiento.
Zero-shot CoT
Zero-shot CoT es un método que puede estimular la capacidad de razonamiento paso a paso sin necesidad de ejemplos.
Solo es necesario añadir al final del prompt una frase de activación como "pensemos paso a paso".
Ejemplo de Zero-shot CoT
prompt = """
Problema: Xiaoming tiene 5 manzanas, Xiaohong le dio 3, Xiaoming se comió 2, ¿cuántas quedan?
Pensemos paso a paso:
"""
# Llamar al modelo de lenguaje
response = llm.generate(prompt)
# Ejemplo de salida del modelo:
# Primer paso: calcular el total de manzanas que recibió Xiao Ming
# Xiaoming tenía 5 manzanas, Xiaohong le dio 3.
# 5 + 3 = 8
#
# Segundo paso: calcular la cantidad después de comer
# Xiaoming se comió 2
# 8 - 2 = 6 unidades
#
# Conclusión: quedan 6 manzanas
Few-shot CoT
El Few-shot CoT, al proporcionar ejemplos con procesos de razonamiento detallados, ayuda al modelo a aprender patrones de razonamiento específicos.
Cuando el Zero-shot CoT no da buenos resultados, se puede probar con Few-shot CoT.
Ejemplo de Few-shot CoT
prompt = """
Ejemplo 1:
Problema: Xiaozhang tiene 10 yuanes, compró 3 libros, cada uno a 2 yuanes, ¿cuánto le queda?
Pensemos paso a paso:
- Xiao Zhang tenía originalmente 10 yuanes
- Cada libro cuesta 2 yuanes, se compran 3 libros, el gasto es 3 × 2 = 6 yuanes
- 10 - 6 = 4 yuan
Respuesta: quedan 4 yuanes
Ejemplo 2:
Problema: un gato atrapa 2 ratones por hora, ¿cuántos atrapa en 8 horas?
Pensemos paso a paso:
- Atrapa 2 ratones por hora
- En 8 horas capturó 8 × 2 = 16
Respuesta: atrapé 16
Pregunta: {user_question}
Pensemos paso a paso:
"""
response = llm.generate(prompt)
Ventajas de la cadena de pensamiento
La técnica de la cadena de pensamiento aporta tres valores fundamentales:
Trazabilidad: descompone el razonamiento complejo en pasos intermedios trazables, lo que facilita la comprensión del proceso de razonamiento.
Explicabilidad: mejora la explicabilidad del modelo y permite a los usuarios saber de dónde proviene la respuesta.
Mejora de la precisión: aumenta significativamente la precisión en tareas de razonamiento complejo, especialmente en tareas matemáticas y lógicas.
Tree of Thoughts(ToT)
El árbol de pensamiento es una extensión de la cadena de pensamiento; ya no se limita al razonamiento lineal.
ToT explora múltiples caminos posibles en cada nodo de razonamiento, formando una estructura de árbol.
Esto permite al Agente realizar exploración de múltiples rutas, retroceder y evaluar globalmente.
Diferencias con la cadena de pensamiento
CoT adopta un razonamiento lineal, donde cada paso depende de la conclusión del paso anterior; es adecuado para problemas con una ruta clara.
ToT adopta un razonamiento espacial, considerando simultáneamente múltiples ramas posibles; es adecuado para problemas que requieren exploración y planificación.
Implementación de código
Implementación básica del agente ToT
"""
Implementación del agente Tree of Thoughts
Mecanismo central: generar múltiples ramas candidatas en cada nodo, evaluarlas y seleccionar la mejor para continuar
"""
def __init__(self, llm, max_depth=4, beam_size=3):
# Instancia de LLM
self.llm = llm
# Profundidad máxima de búsqueda
self.max_depth = max_depth
# Número de nodos candidatos retenidos por capa (beam width)
self.beam_size = beam_size
def solve(self, problem):
"""
Usar el marco ToT para resolver el problema
:param problem: descripción del problema
:return: la solución óptima
"""
# Crear el nodo raíz, conteniendo la pregunta como estado inicial
root = ThoughtNode(problem, depth=0)
# Inicializar la lista de nodos frontera (nodos a expandir).
frontier = [root]
# Expandir por capas
for depth in range(self.max_depth):
# Almacenar todos los nodos candidatos
all_candidates = []
# Recorrer todos los nodos frontera
for node in frontier:
# Generar múltiples candidatos para el siguiente paso del nodo actual
candidates = self.llm.generate_thoughts(
node.content, # Contenido del nodo actual
n=self.beam_size # Generar cantidad
)
# Crear un nuevo nodo y agregarlo a la lista de candidatos
for cand in candidates:
all_candidates.append(
ThoughtNode(cand, depth + 1, parent=node)
)
# Evaluar todos los nodos candidatos
evaluated = self.evaluator.rank(all_candidates)
# Seleccionar los beam_size nodos óptimos como frontera de la siguiente ronda
frontier = evaluated[:self.beam_size]
# Verificar si se encontró una solución
if self.is_solution(frontier):
break
# Retroceder para encontrar la solución óptima
return self.backtrack_best(frontier)
Escenarios de aplicación
ToT es especialmente adecuado para escenarios que requieren tomar decisiones o planificar:
Escritura creativa: genera múltiples direcciones de desarrollo para una historia, evalúa y selecciona la mejor.
Planificación estratégica: evaluar los resultados potenciales de múltiples opciones de acción.
Decisiones complejas: problemas de decisión que requieren considerar múltiples posibilidades.
Planificación de tareas y MCTS
La búsqueda en árbol de Monte Carlo (Monte Carlo Tree Search, MCTS) es un algoritmo de búsqueda heurística para problemas de decisión complejos.
MCTS tiene una amplia aplicación en la planificación de agentes, especialmente adecuado para IA de juegos y planificación de tareas complejas.
Idea central
MCTS evalúa el valor potencial de cada nodo de decisión mediante la simulación de juegos aleatorios.
No necesita evaluar todas las rutas posibles, sino que guía la dirección de la búsqueda mediante muestreo y estadísticas.
Los cuatro pasos del MCTS
Primer paso, selección (Selection): comienza desde el nodo raíz y selecciona recursivamente el mejor nodo hijo hasta alcanzar un nodo hoja.
Al seleccionar, se utiliza la fórmula UCB (Upper Confidence Bound) para equilibrar la exploración y la explotación.
Segundo paso, expansión (Expansion): se añaden uno o más nodos hijos al nodo hoja.
Tercer paso, simulación (Simulation): desde el nuevo nodo, se simula aleatoriamente el juego hasta el final.
Cuarto paso, retropropagación (Backpropagation): actualizar la información estadística de todos los nodos en la trayectoria simulada.
Implementación de código
Implementación del planificador MCTS
class MCTSNode:
"""
Nodo del árbol MCTS
Almacena el estado del nodo, los datos estadísticos y las relaciones de nodos hijos
"""
def __init__(self, state, parent=None, action=None):
# Estado actual
self.state = state
Referencia al nodo padre
self.parent = parent
# Acción para llegar a este nodo desde el nodo padre
self.action = action
Lista de nodos hijos
self.children = []
# Número de visitas de este nodo
self.visit_count = 0
# Valor de recompensa acumulada de este nodo
self.reward = 0.0
def is_fully_expanded(self):
"""Comprobar si se han expandido todos los posibles nodos hijos"""
return len(self.children) > 0
def is_terminal(self):
"""Comprueba si es un nodo terminal (juego terminado o objetivo alcanzado)"""
return self.state.is_terminal()
def uct_child(self):
"""
Seleccionar el subnodo óptimo utilizando la fórmula UCT
UCT = reward/visits + C * sqrt(ln(parent_visits)/visits)
C es la constante de exploración, normalmente establecida en sqrt(2)
"""
# Constante de exploración, equilibrando exploración y explotación
C = math.sqrt(2)
return max(
self.children,
key=lambda c: c.reward / c.visit_count +
C * math.sqrt(math.log(self.visit_count) / c.visit_count)
)
class MCTSPlanner:
"""
Planificador MCTS
Usar la búsqueda de árbol de Monte Carlo para generar la planificación de acciones para el agente.
"""
def __init__(self, simulation_limit=1000, exploration_constant=1.41):
# Número máximo de simulaciones
self.simulation_limit = simulation_limit
# Explorar constantes
self.exploration_constant = exploration_constant
def plan(self, initial_state):
"""
Planificar desde el estado inicial
:param initial_state: estado inicial
:return: acción óptima
"""
Crear nodo raíz
root = MCTSNode(initial_state)
# Ejecutar múltiples simulaciones
for _ in range(self.simulation_limit):
# 1. Selección: desde el nodo raíz, selecciona hacia abajo el mejor nodo hijo hasta el nodo hoja
node = self._selection(root)
# 2. Expansión: si no es un nodo terminal, expandir un nuevo nodo
if not node.is_terminal():
node = self._expansion(node)
# 3. Simulación: desde un nuevo nodo, simular aleatoriamente hasta la terminación
reward = self._simulation(node)
# 4. Retropropagación: actualizar las estadísticas de todos los nodos en la ruta
self._backpropagation(node, reward)
# Devuelve la acción correspondiente al nodo hijo óptimo del nodo raíz
return root.best_child().action
def _selection(self, node):
"""Fase de selección: seleccionar el mejor subnodo"""
while node.is_fully_expanded() and not node.is_terminal():
node = node.uct_child()
return node
def _expansion(self, node):
"""Fase de expansión: añadir nuevo nodo hijo"""
# Generar todas las acciones posibles
possible_actions = node.state.get_possible_actions()
# Crear un nodo hijo para cada acción
for action in possible_actions:
new_state = node.state.apply_action(action)
child = MCTSNode(new_state, parent=node, action=action)
node.children.append(child)
# Devolver un nodo hijo aleatorio (también se puede usar una estrategia determinista)
return node.children[0]
def _simulation(self, node):
"""Fase de simulación: simulación aleatoria hasta el final del juego"""
state = node.state
while not state.is_terminal():
# Selección aleatoria de acciones
actions = state.get_possible_actions()
action = random.choice(actions)
state = state.apply_action(action)
# Devolver la recompensa final
return state.get_reward()
def _backpropagation(self, node, reward):
"""Retropropagación: actualizar las estadísticas"""
while node is not None:
node.visit_count += 1
node.reward += reward
node = node.parent
Nota: MCTS tiene un costo computacional alto; es adecuado para escenarios que requieren planificación profunda pero con condiciones de terminación claras. Para tareas con altos requisitos de tiempo real, puede ser necesario limitar el número de simulaciones o usar otros métodos.
Reflexion (autorreflexión)
Reflexion es una técnica que dota al Agente de capacidad de autorreflexión.
Al añadir un mecanismo de reflexión al Agente, este puede analizar las causas del error después de un fallo, ajustar la estrategia e intentarlo de nuevo.
Idea central
El agente no solo ejecuta acciones, sino que también observa los resultados y reflexiona: ¿por qué fallé? ¿cómo debería mejorar la próxima vez?
Esta capacidad es crucial para el aprendizaje continuo y la automejora.
Implementación de código
Implementación del Agente Reflexion
"""
Agente con capacidad de autorreflexión
Mecanismo central: Ejecutar -> Evaluar -> Reflexionar -> Reintentar
"""
def __init__(self, actor, reviewer, max_retries=3):
# Ejecutor: responsable de ejecutar tareas específicas
self.actor = actor
# Evaluador: responsable de evaluar los resultados de la ejecución
self.reviewer = reviewer
# Número máximo de reintentos
self.max_retries = max_retries
def run(self, task):
"""
Ejecutar una tarea con autorreflexión
:param task: descripción de la tarea
:return: Resultado de la ejecución
"""
# Mantener el historial de ejecución
history = []
for attempt in range(self.max_retries):
# Fase 1: Intentar ejecutar la tarea
result = self.actor.execute(task, history)
# Registrar este intento
history.append({
"attempt": attempt,
"result": result
})
# Fase 2: Revisar resultados
feedback = self.reviewer.evaluate(task, result)
# Fase 3: Verificar si se logró
if feedback.is_success:
return result
# Fase 4: Reflexionar sobre las causas del fracaso
# Generar una nueva indicación de estrategia para guiar el próximo intento
reflection = self.reviewer.reflect(
task, # Tarea original
result, # Resultado fallido
feedback # Comentarios de revisión
)
# Agregar el resultado de la reflexión al historial para referencia en el próximo intento
history.append({
"type": "reflection",
"content": reflection
})
# Si todos los reintentos fallan, devolver el historial
return history
class Reviewer:
"""Evaluador: evalúa los resultados de la ejecución y genera reflexiones"""
def evaluate(self, task, result):
"""
Evaluar el resultado de ejecución
:return: el objeto que contiene si fue exitoso y la retroalimentación detallada.
"""
# Comprobar si el resultado cumple con los requisitos de la tarea
is_success = self.check_success(task, result)
if is_success:
return EvaluationResult(is_success=True)
else:
# Generar análisis de la causa del fallo
failure_reasons = self.analyze_failures(task, result)
return EvaluationResult(
is_success=False,
reasons=failure_reasons
)
def reflect(self, task, result, feedback):
"""
Generar contenido de reflexión
Ayudar al Actor a entender las causas del fallo y mejorar la estrategia
"""
prompt = f"""
Tarea: {task}
Resultado de ejecución: {result}
Motivo del fallo: {feedback.reasons}
Analice las causas del fallo y proporcione sugerencias de mejora.
Notas importantes:
1. ¿Qué salió mal?
2. ¿Cómo debería evitarse la próxima vez?
3. ¿Qué estrategia se debe cambiar?
"""
return self.llm.generate(prompt)
Escenarios de aplicación
Reflexion es especialmente adecuado para los siguientes escenarios:
Tareas que requieren mejora continua, como sistemas de diálogo, generación de código, etc.
Escenarios donde el costo del error es alto pero el costo de reintentar es bajo.
Situaciones en las que se necesita aprender de los fracasos.
Estrategia de descomposición de tareas
Las tareas complejas generalmente deben descomponerse en subtareas manejables.
La descomposición efectiva de tareas es la base de la capacidad de planificación.
Descomposición recursiva de tareas
Descomponer la tarea recursivamente en subtareas más pequeñas hasta que puedan ejecutarse directamente.
Descomposición recursiva de tareas
"""
Descomponer tareas recursivamente
:param task: La tarea a descomponer.
:param is_executable_fn: función que determina si la tarea se puede ejecutar directamente
:param decompose_fn: función que descompone la tarea
:return: lista de tareas ejecutables
"""
# Si la tarea puede ejecutarse directamente, retornar directamente
if is_executable_fn(task):
return [task]
# Descomponer la tarea en subtareas
subtasks = decompose_fn(task)
# Descomponer recursivamente cada subtarea
result = []
for subtask in subtasks:
# Llamar recursivamente a la función de descomposición para las subtareas
result.extend(
decompose(subtask, is_executable_fn, decompose_fn)
)
return result
# Ejemplo: determinar si la tarea es ejecutable
def is_code_complete(task):
Comprobar si la tarea puede ejecutar código directamente
return task.get("type") == "code" and len(task.get("dependencies", [])) == 0
# Ejemplo: descomponer tareas complejas
def decompose_programming_task(task):
"""Descomponer la tarea de programación"""
if "write_function" in task["type"]:
return [
{"type": "understand_requirements", "task": task["spec"]},
{"type": "write_code", "spec": task["spec"]},
{"type": "write_tests", "function": task["function_name"]},
{"type": "verify_tests", "function": task["function_name"]}
]
return [task]
Descomposición paralela de tareas
Identificar subtareas independientes que puedan ejecutarse en paralelo para mejorar la eficiencia.
Esta es una estrategia clave para acelerar la ejecución de tareas.
Descomposición jerárquica de tareas
Dividir las tareas en diferentes niveles de abstracción; las tareas de alto nivel invocan a las de bajo nivel, formando un árbol jerárquico de tareas.
Adecuado para sistemas complejos que requieren múltiples niveles de abstracción.
Modo Plan-and-Execute
Plan-and-Execute es un patrón arquitectónico que separa la planificación de la ejecución.
El Agente primero planifica completamente todo el flujo de la tarea y luego ejecuta según el plan.
Diferencias con ReAct
ReAct razona y ejecuta simultáneamente; es más flexible pero la ruta puede ser inestable.
Plan-and-Execute planifica primero y ejecuta después; es más estable pero carece de capacidad de ajuste dinámico.
Implementación de código
Implementación del agente Plan-and-Execute
"""
Agente de la arquitectura Plan-and-Execute
Mecanismo central: primero planificar completamente y luego ejecutar según el plan
"""
def __init__(self, planner, executor):
# Planificador: responsable de elaborar el plan de ejecución
self.planner = planner
# Ejecutor: responsable de ejecutar los pasos específicos
self.executor = executor
def run(self, task):
"""
Ejecutar tareas
Se divide en fase de planificación y fase de ejecución
"""
# ==================== Fase de planificación ====================
# Generar el plan de ejecución completo de una sola vez
plan = self.planner.create_plan(task)
# ==================== Fase de ejecución ====================
results = []
# Ejecutar en el orden planificado
for step in plan.steps:
# Ejecutar el paso actual
result = self.executor.execute(step)
results.append(result)
# Verificar si se necesita replanificar
# Por ejemplo: el resultado de la ejecución no coincide con lo esperado, o se produce una situación inesperada
if self.needs_replan(results):
# Replanificar basándose en los resultados actuales
plan = self.planner.replan(task, results)
# Integrar todos los resultados
return self.summarize(results)
def needs_replan(self, results):
"""
Determinar si es necesario replanificar
Verificar si el resultado de la ejecución cumple con lo esperado
"""
# Obtener el último resultado
last_result = results[-1]
# Si el resultado no cumple claramente lo esperado, se necesita replanificar.
if last_result.is_unexpected():
return True
# Si el resultado provoca que los pasos siguientes no se puedan ejecutar, es necesario replanificar
if last_result.blocks_future():
return True
return False
class Planner:
"""Planificador: generar el plan de ejecución de la tarea"""
def create_plan(self, task):
"""Crear el plan de ejecución inicial"""
prompt = f"""
Tarea: {task}
Por favor, elabore un plan de ejecución detallado, incluyendo:
1. El orden de los pasos a ejecutar
2. La operación específica de cada paso
3. Dependencias entre pasos
Formato de salida:
Paso 1: [Descripción de la operación]
Paso 2: [Descripción de la operación]
...
"""
plan_text = self.llm.generate(prompt)
return Plan.parse(plan_text)
def replan(self, task, results):
"""Replanificar basado en los resultados de ejecución"""
# Analizar los resultados completados
completed = [r for r in results if r.is_success]
failed = [r for r in results if not r.is_success]
# Generar el plan ajustado
prompt = f"""
Tarea original: {task}
Pasos completados: {completed}
Paso fallido: {failed}
Por favor, elabore el plan de ejecución ajustado:
"""
plan_text = self.llm.generate(prompt)
return Plan.parse(plan_text)
class Executor:
"""Ejecutor: ejecuta los pasos concretos del plan."""
def execute(self, step):
"""Ejecutar un solo paso"""
# Seleccionar el método de ejecución según el tipo de paso
if step.type == "tool_call":
return self.execute_tool_call(step)
elif step.type == "code":
return self.execute_code(step)
elif step.type == "query":
return self.execute_query(step)
return Result(success=False, error="Unknown step type")
Nota: el patrón Plan-and-Execute es adecuado para escenarios donde la estructura de la tarea es relativamente estable y puede planificarse con antelación. Para entornos que requieren flexibilidad, el modo ReAct puede ser más adecuado.
Resumen del capítulo
Este capítulo presenta las capacidades centrales de razonamiento y planificación de los Agentes de IA.
ReActEl marco, mediante el ciclo de razonamiento y acción, permite al Agente ajustar su estrategia mientras ejecuta.
Cadena de pensamiento (CoT)Al mostrar el proceso de razonamiento paso a paso, se mejora la precisión en la ejecución de tareas complejas.
Árbol de pensamiento (ToT)Extiende la cadena de pensamiento, soportando exploración de múltiples rutas y retroceso.
MCTSEs un algoritmo de búsqueda heurística, adecuado para problemas complejos de decisión y planificación.
ReflexionDota al Agente de capacidad de autorreflexión para aprender de los fracasos y mejorar.
Estrategia de descomposición de tareasEs la base de la capacidad de planificación, incluyendo descomposición recursiva, descomposición paralela y descomposición jerárquica.
Plan-and-ExecuteAdopta el modo de planificar primero y ejecutar después, adecuado para tareas con estructura estable.
Estas técnicas pueden usarse individualmente o combinarse para construir sistemas de Agentes más potentes.
Otras extensiones