Skip to content

Bucle principal del agente

源码版本v2026.7.20

Responsabilidad

run_conversation es el corazón de Hermes: recibe un mensaje del usuario, llama al modelo y a las herramientas de forma repetida hasta que se agota el presupuesto (budget) de iteración (iteration) o la tarea se completa. Coordina también la construcción del contexto (context), el despacho (dispatch) de herramientas (tool), la decisión de parada y el fallback ante fallos.

El archivo ocupa unas 5800 líneas con alta densidad lógica; esta página solo cubre la línea principal; los detalles quedan en subpáginas.

Archivos clave

Flujo de datos

  1. run_agent.py AIAgent es un reenviador fino; la inyección de dependencias real ocurre en init_agent.
  2. El llamador pasa user_message; run_conversation primero pasa por build_turn_context para ensamblar el contexto del turno (api_messages).
  3. Entra al bucle while principal: comprobar interrupciones → decrementar presupuesto (iteration_budget.consume()) → construir/sanitizar api_messages → precompresión → vaciar texto /steer → agregación MoA → entrar al subbucle de reintentos de API.
  4. Tras la respuesta del modelo, valida y procesa finish_reason; en caso de fallo, ejecuta agent._try_activate_fallback().
  5. Si se alcanza el umbral de compresión, llama a context_compressor para liberar espacio.
  6. Al salir del bucle (presupuesto agotado / tarea completada / interrupción) devuelve el mensaje final, que la capa de gateway entrega (ver capa de gateway).

El aspecto real del bucle principal está en agent/conversation_loop.py:721-740:

python
while (api_call_count < agent.max_iterations and agent.iteration_budget.remaining > 0) or agent._budget_grace_call:
    agent._checkpoint_mgr.new_turn()
    if agent._interrupt_requested:
        interrupted = True
        _turn_exit_reason = "interrupted_by_user"
        break
    api_call_count += 1
    if agent._budget_grace_call:
        agent._budget_grace_call = False
    elif not agent.iteration_budget.consume():
        _turn_exit_reason = "budget_exhausted"
        break

El or agent._budget_grace_call añadido a la condición es el punto de apoyo para «dar una oportunidad más cuando el presupuesto ya está a cero»: al entrar en el turno el flag se limpia de inmediato, así que «una oportunidad» es literalmente una.

Los reintentos de API no son recursivos, sino un while retry_count < max_retries interno. Aquí está el inicio de agent/conversation_loop.py:1227-1245:

python
while retry_count < max_retries:
    if agent.provider == "nous":
        try:
            from agent.nous_rate_guard import nous_rate_limit_remaining
            if nous_rate_limit_remaining() is not None and nous_rate_limit_remaining() > 0:
                if agent._try_activate_fallback():
                    active_system_prompt = _sync_failover_system_message(
                        agent, api_messages, active_system_prompt)
                    retry_count = 0
                    continue
                return {"final_response": "⏳ rate-limited, no fallback.",
                        "messages": messages, "completed": False, "failed": True}
        except Exception:
            pass  # Never let rate guard break the agent loop

Tras cambiar de provider, retry_count = 0 reinicia desde un estado limpio. El except Exception: pass más externo es deliberado: el rate guard es una optimización; no debe, bajo ninguna circunstancia, romper el bucle principal.

Motivo de diseño

¿Por qué los reintentos son un subbucle y no recursión? La recursión en Python consume call stack (profundidad limitada), y el estado de cada fallo se reparte entre varios stack frames difíciles de limpiar a la vez. Con while retry_count < max_retries, todo el estado del reintento (retry_count, compression_attempts) son variables locales en el mismo frame; al cambiar de fallback, un único retry_count = 0 lo resetea entero.

¿Por qué existe el grace call? El presupuesto es un tope duro, pero a veces al modelo le faltan una o dos llamadas a herramienta para terminar; cortar de plano pierde el trabajo ya hecho. _budget_grace_call lo activa el bucle interno cuando detecta que «está a punto de terminar»; el or externo permite que ese turno se complete, pero el flag se limpia en cuanto entra — «una oportunidad» es realmente una.

¿Por qué un objeto iteration_budget en lugar de un simple contador? El presupuesto es accedido desde varios hilos (subagentes, refund de compresión); IterationBudget envuelve consume/refund con un lock, garantizando thread safety y dejando que el compresor, tras eliminar mensajes viejos, llame refund() para devolver iteraciones. Un entero simple no cubre ninguna de las dos cosas.

Límites y fallos

  • Presupuesto agotado a mitad: antes de salir, _persist_session(messages, conversation_history) vuelca la conversación a disco; _turn_exit_reason se marca como "budget_exhausted", para que la capa de arriba distinga fin normal de corte.
  • Excepción de una herramienta: el error que lanza la herramienta lo atrapa el bucle interno de reintentos; al agotar max_retries, se llama agent._try_activate_fallback(). Si la cadena de fallback está vacía, se devuelve un dict con failed: True en lugar de propagar la excepción — el gateway de arriba solo ve un fallo estructurado.
  • El rate guard falla por sí mismo: si el import de nous_rate_guard falla o su llamada lanza, el except Exception lo cubre y el guard se degrada a «no aplica»; el bucle principal sigue por su camino original. El comentario lo dice claro: "Never let rate guard break the agent loop".
  • Pool OAuth que se refresca infinitamente: _auth_pool_refresh_counts se resetea al inicio de cada turn, para que una entrada del pool OAuth no quede «refrescándose con éxito» indefinidamente bajo 401 continuos (parche para #26080).

Resumen

El bucle principal solo orquesta: impulso de turnos, control de presupuesto, reintentos y fallback. La capacidad real viene de las herramientas de la capa de capacidades y las habilidades; la entrega multiplataforma la gestiona la capa de gateway; la autoevolución la impulsa el bucle de aprendizaje.

Para la presentación oficial en chino, ver README.zh-CN y website/i18n/zh-Hans/.

Sitio de aprendizaje comunitario no oficial. Basado en el código fuente de NousResearch/hermes-agent (licencia MIT).