Skip to content

Hauptschleife des Agenten

源码版本v2026.7.20

Verantwortung

run_conversation ist das Herz von Hermes: Es nimmt eine Nutzernachricht, ruft wiederholt Modell + Werkzeuge (tools) auf, bis das Iterationsbudget (iteration budget) aufgebraucht oder die Aufgabe erledigt ist. Es koordiniert zugleich Kontextaufbau (context build), Werkzeug-Dispatch (tool dispatch), Stopp-Bedingungs-Prüfung sowie Fallback (fallback) bei Misserfolg.

Die Datei hat insgesamt etwa 5800 Zeilen und hohe logische Dichte – diese Seite betrachtet nur die Hauptlinie, Details werden auf Unterseiten behandelt.

Schlüsseldateien

Datenfluss

  1. run_agent.py AIAgent ist ein dünner Forwarder; die echte Dependency-Injection passiert in init_agent.
  2. Der Aufrufer übergibt user_message; run_conversation läuft zuerst durch build_turn_context, um den Turn-Kontext (turn context) (api_messages) aufzubauen.
  3. Eintritt in die while-Hauptschleife: Abbruchprüfung → Budget (budget) dekrementieren (iteration_budget.consume()) → api_messages aufbauen/desinfizieren → Vorkompression → /steer-Texte leeren → MoA-Aggregation → Eintritt in die API-Aufruf-Wiederholungs-Teilschleife.
  4. Nach der Modell-Rückgabe erfolgen Response-Validierung und finish_reason-Behandlung; bei Misserfolg greift agent._try_activate_fallback().
  5. Wird die Kompressionsschwelle (compression threshold) getroffen, läuft context_compressor und räumt Platz zurück.
  6. Nach Schleifenende (Budget erschöpft / Aufgabe erledigt / abgebrochen) wird die finale Nachricht zurückgegeben und vom Gateway (gateway) zugestellt (siehe Gateway-Schicht).

Das echte Gesicht der Hauptschleife steht in 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

Der angehängte or agent._budget_grace_call ist der Hebel «nach Budget-Null noch eine Chance»: Zu Beginn dieses Turn wird das Flag sofort gelöscht, deshalb bleibt es bei «einer Chance».

Die API-Wiederholung ist nicht rekursiv, sondern eine innere while retry_count < max_retries. Hier der Anfang aus 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

Nach dem Provider-Wechsel setzt retry_count = 0 den Zähler aus einem sauberen Zustand zurück. Das äußerste except Exception: pass ist Absicht: Der Guard ist eine Optimierung und darf niemals die Hauptschleife selbst brechen.

Designmotiv

Warum ist die Wiederholung als Teilschleife statt als Rekursion gebaut? Rekursion frisst in Python die Aufruf-Stack-Tiefe, und der Zustand eines Fehlers verstreut über mehrere Stack-Frames, was schwer einheitlich aufzuräumen ist. Mit while retry_count < max_retries liegen alle Wiederholungszustände (retry_count, compression_attempts) als lokale Variablen im selben Frame; beim Fallback-Wechsel genügt ein retry_count = 0 zum kompletten Reset.

Warum ein Grace-Call? Das Budget ist eine harte Obergrenze, aber das Modell ist manchmal nur ein oder zwei Werkzeugaufrufe vom Abschluss entfernt — direktes Abwürgen würde bereits geleistete Arbeit wegwerfen. _budget_grace_call wird von innen gesetzt, wenn «fast geschafft», der äußere or lässt diesen Turn durchlaufen, aber das Flag wird beim Eintritt sofort gelöscht — «eine Chance» bleibt eine.

Warum ein iteration_budget-Objekt statt ein simpler Zähler? Das Budget wird threadübergreifend angesprochen (Subagent, Kompressions-Refund); IterationBudget kapselt consume/refund mit einer Sperre und garantiert Thread-Sicherheit, lässt aber dem Kompressor nach dem Entfernen alter Nachrichten den Weg, über refund() Iterations-Guthaben zurückzugeben. Eine einfache Ganzzahl kann beides nicht.

Grenzen und Fehler

  • Budget mid-turn erschöpft: Vor dem Austritt schreibt _persist_session(messages, conversation_history) die aktuelle Sitzung (session) auf Platte; _turn_exit_reason wird "budget_exhausted" gesetzt, damit die Oberschicht zwischen normalem Ende und Abbruch unterscheiden kann.
  • Werkzeugaufruf-Ausnahme: Fehler auf Werkzeugseite werden von der inneren Wiederholung gefangen; nach max_retries läuft agent._try_activate_fallback(); ist die Fallback-Kette leer, kommt ein failed: True-Ergebnisdict zurück statt einer nach außen sichtbaren Exception — das Gateway oben sieht nur strukturiertes Scheitern.
  • Guard des Rate-Limits selbst fehlerhaft: Schlägt der Import oder Aufruf von nous_rate_guard fehl, fängt except Exception es ab; der Guard degradiert zu «nicht wirksam», die Hauptschleife läuft weiter auf dem regulären Pfad; der Kommentar sagt es direkt: «Never let rate guard break the agent loop».
  • OAuth-Pool unendlich Refresh: _auth_pool_refresh_counts wird zu Turn-Beginn zurückgesetzt und verhindert, dass ein einzelner OAuth-Pool unter dauerndem 401 von try_refresh_current() unendlich «erfolgreich» refreshed wird (Auffangwehr aus #26080).

Zusammenfassung

Die Hauptschleife orchestriert nur: Turn-Antrieb, Budget-Steuerung, Wiederholung und Fallback. Die echte Fähigkeit kommt von Tools und Skills der Fähigkeitsschicht, die Multi-Plattform-Zustellung obliegt der Gateway-Schicht, die Selbst-Evolution treibt die Lernschleife.

Für den Abgleich mit der offiziellen chinesischen Einführung siehe README.zh-CN und website/i18n/zh-Hans/.

Inoffizielle Community-Lernseite. Basiert auf dem MIT-lizenzierten NousResearch/hermes-agent-Quellcode.