Das formale Framework hinter selbstlernenden Agenten: Wie Reinforcement Learning auf Code-Generierung abgebildet wird – von Markov-Entscheidungsprozessen über Q-Learning bis zur Epsilon-Greedy-Strategie.
1. Vom Feedback-Loop zum formalen Framework
Der vorherige Artikel beschreibt drei Säulen selbstlernender Agenten: Feedback-Loops, persistentes Gedächtnis und Selbstkritik. Der Feedback-Loop – Agent generiert, Umgebung bewertet, Agent passt an – folgt einem Muster, das in der KI-Forschung seit Jahrzehnten formalisiert ist: Reinforcement Learning (RL).
"The idea of RL is that an agent can learn from the environment by interacting with it and receiving rewards for performing actions." — Raschka et al., Machine Learning with PyTorch and Scikit-Learn, S. 1025
RL liefert die mathematische Struktur, um Feedback-Loops präzise zu beschreiben: Welche Zustände existieren? Welche Aktionen stehen zur Verfügung? Wie wird Erfolg gemessen? Wann lohnt es sich, Neues auszuprobieren, und wann die bewährte Strategie zu verfolgen?
2. Markov-Entscheidungsprozess: Das Modell
Ein Markov-Entscheidungsprozess (MDP) formalisiert die Interaktion zwischen Agent und Umgebung durch vier Komponenten:
| Komponente | Symbol | Beschreibung |
|---|---|---|
| Zustände | S | Menge aller möglichen Situationen |
| Aktionen | A | Menge aller verfügbaren Handlungen |
| Übergangsfunktion | T(s, a, s') | Wahrscheinlichkeit, von s nach s' zu gelangen |
| Reward-Funktion | R(s, a) | Belohnung für Aktion a in Zustand s |
Mapping auf Code-Generierung
Die Übersetzung auf einen Code-Agenten ergibt ein konkretes MDP:
| RL-Konzept | Code-Agent |
|---|---|
| State | Aufgabentyp + Komplexität + bisherige Versuche + Test-Pass-Rate |
| Action | Strategie-Wahl: generieren, Fehler beheben, refaktorieren, Tests ergänzen |
| Transition | Aufgabe → Code-Generierung → Test-Ausführung → neuer Zustand |
| Reward | Gewichtetes Signal aus Testergebnissen, Code-Qualität, Effizienz |
@dataclass(frozen=True)
class CodeState:
task_type: str # "bugfix", "feature", "refactor"
complexity: int # 1–5
attempts: int # Bisherige Versuche
test_pass_rate: float # 0.0–1.0
@dataclass(frozen=True)
class CodeAction:
strategy: str # "generate", "fix_errors", "refactor", "add_tests"
context_level: int # 1–3: minimal, normal, maximal
Die Markov-Eigenschaft besagt: Der nächste Zustand hängt nur vom aktuellen Zustand und der gewählten Aktion ab – nicht von der gesamten Historie. Für Code-Agenten eine vereinfachende Annahme, die durch das persistente Gedächtnis (Artikel 01) kompensiert wird.
3. Reward Shaping: Differenziertes Feedback
❌ Binäres Feedback: Pass oder Fail
Ein naives Reward-Signal – 1.0 für bestandene Tests, 0.0 für Fehlschlag – liefert zu wenig Information. Der Agent erfährt nicht, wie nah er an einer Lösung war.
✅ Gewichtetes Reward-Signal
Reward Shaping zerlegt die Bewertung in multiple Metriken mit unterschiedlicher Gewichtung:
def compute_reward(
test_pass_rate: float,
quality_score: float,
efficiency_score: float,
weights: dict[str, float] | None = None,
) -> CodeReward:
w = weights or {"test": 0.6, "quality": 0.25, "efficiency": 0.15}
total = (
w["test"] * test_pass_rate
+ w["quality"] * quality_score
+ w["efficiency"] * efficiency_score
)
return CodeReward(
test_score=test_pass_rate,
quality_score=quality_score,
efficiency_score=efficiency_score,
total=round(total, 4),
)
Die Gewichtung spiegelt eine klare Priorität: Tests dominieren (60 %), Code-Qualität folgt (25 %), Effizienz ergänzt (15 %). Diese Gewichte sind konfigurierbar – ein Team kann die Balance an eigene Standards anpassen.
Fortschritts-Bonus
Zusätzlich zum absoluten Score belohnt ein Shaping-Term den Fortschritt gegenüber dem vorherigen Zustand:
def shaped_reward(
current_test_rate: float,
previous_test_rate: float,
base_reward: float,
shaping_factor: float = 0.5,
) -> float:
progress = current_test_rate - previous_test_rate
return round(base_reward + shaping_factor * progress, 4)
Ein Agent, der die Test-Pass-Rate von 0.3 auf 0.8 steigert, erhält einen höheren Reward als einer, der konstant bei 0.8 bleibt – auch wenn der absolute Score identisch ist. Das beschleunigt das Lernen in frühen Phasen.
4. Q-Learning: Werte für State-Action-Paare
Die Q-Funktion
Die zentrale Frage im RL lautet: Welche Aktion ist in einem gegebenen Zustand optimal? Die Q-Funktion Q(s, a) beantwortet diese Frage – sie schätzt den erwarteten kumulierten Reward für Aktion a in Zustand s.
"The action value function determines the long-term rewards the agent will receive for taking action a. This function is usually expressed as Q(S, a), and hence is also called the Q-function." — Ashish Ranjan Jha, Mastering PyTorch, S. 393
Die Bellman-Gleichung
Q-Werte werden nicht direkt berechnet, sondern iterativ über die Bellman-Gleichung aktualisiert:
Q(s, a) ← Q(s, a) + α · [r + γ · max_a' Q(s', a') − Q(s, a)]
| Parameter | Bedeutung | Typischer Wert |
|---|---|---|
| α (Alpha) | Lernrate: Wie stark neue Erfahrungen gewichtet werden | 0.1 |
| γ (Gamma) | Diskontierungsfaktor: Wie wichtig zukünftige Rewards sind | 0.9 |
| r | Erhaltener Reward | variabel |
| max Q(s', a') | Bester geschätzter Wert im Folgezustand | berechnet |
"The Bellman equation is one of the central elements of many RL algorithms. The Bellman equation simplifies the computation of the value function, such that rather than summing over multiple time steps, it uses a recursion." — Raschka et al., Machine Learning with PyTorch and Scikit-Learn, S. 1025
Implementierung: Q-Table
Für diskrete State-Action-Räume speichert eine Q-Table die Werte als Dictionary:
@dataclass
class QTable:
values: dict[tuple[str, str], float] = field(default_factory=dict)
default_value: float = 0.0
def update(
self, state_key, action_key, reward,
next_state_key, actions, alpha=0.1, gamma=0.9,
) -> float:
current_q = self.get(state_key, action_key)
max_next_q = max(
(self.get(next_state_key, a) for a in actions),
default=self.default_value,
)
new_q = current_q + alpha * (reward + gamma * max_next_q - current_q)
self.values[(state_key, action_key)] = round(new_q, 6)
return new_q
def best_action(self, state_key, actions) -> str:
return max(actions, key=lambda a: self.get(state_key, a))
Nach genügend Episoden konvergieren die Q-Werte: Der Agent „weiß", welche Strategie in welcher Situation den höchsten erwarteten Reward liefert.
5. Exploration vs. Exploitation
Das Dilemma
Ein Agent, der immer die Aktion mit dem höchsten bekannten Q-Wert wählt (Exploitation), verpasst möglicherweise bessere Strategien. Ein Agent, der nur zufällig handelt (Exploration), nutzt sein Wissen nicht.
Epsilon-Greedy-Strategie
Die Epsilon-Greedy-Strategie löst das Dilemma mit einem einfachen Mechanismus:
- Mit Wahrscheinlichkeit ε: zufällige Aktion (Exploration)
- Mit Wahrscheinlichkeit 1 − ε: beste bekannte Aktion (Exploitation)
def epsilon_greedy(
q_table: QTable,
state_key: str,
actions: list[str],
epsilon: float,
rng: random.Random | None = None,
) -> tuple[str, bool]:
r = rng.random() if rng else random.random()
if r < epsilon:
chosen = rng.choice(actions) if rng else random.choice(actions)
return chosen, True
return q_table.best_action(state_key, actions), False
"Under this mechanism, at each episode, an epsilon value is pre-decided, which is a scalar value between 0 and 1. [...] If the generated number is less than the pre-defined epsilon value, the agent takes a random action (exploration), otherwise it takes the action with the highest Q-value (exploitation)." — Ashish Ranjan Jha, Mastering PyTorch, S. 393
Epsilon-Decay
Zu Beginn exploriert der Agent stark (hohes ε), mit zunehmender Erfahrung verlässt er sich stärker auf gelerntes Wissen (niedriges ε). Exponentieller Decay modelliert diesen Übergang:
def decay_epsilon(
epsilon: float,
min_epsilon: float = 0.01,
decay_rate: float = 0.005,
episode: int = 0,
) -> float:
decayed = min_epsilon + (epsilon - min_epsilon) * math.exp(-decay_rate * episode)
return round(decayed, 6)
| Episode | ε (bei ε₀=1.0, min=0.01) | Verhalten |
|---|---|---|
| 0 | 1.00 | Reine Exploration |
| 100 | 0.61 | Überwiegend Exploration |
| 500 | 0.09 | Überwiegend Exploitation |
| 1000 | 0.02 | Fast reine Exploitation |
Für Code-Agenten bedeutet das: In den ersten Aufgaben werden verschiedene Strategien (generieren, refaktorieren, Fehler beheben) ausprobiert. Mit wachsender Erfahrung kristallisiert sich heraus, welche Strategie für welchen Aufgabentyp optimal funktioniert.
6. Der Code-Agent-Environment-Loop
Alle Komponenten zusammen ergeben den vollständigen Agent-Environment-Loop:
┌─────────────┐
│ CodeState │ ◄───────────────────────────┐
└──────┬──────┘ │
▼ │
┌─────────────┐ │
│ ε-Greedy │ ── Aktion wählen │
└──────┬──────┘ │
▼ │
┌─────────────┐ │
│ Environment │ ── Code ausführen, Tests │
└──────┬──────┘ │
▼ │
┌─────────────┐ │
│ Reward │ ── Shaping + Fortschritt │
└──────┬──────┘ │
▼ │
┌─────────────┐ │
│ Q-Update │ ── Bellman-Gleichung │
└──────┬──────┘ │
▼ │
┌─────────────┐ │
│ Next State │ ─────────────────────────────►┘
└─────────────┘
Die run_episode-Funktion orchestriert einen kompletten Durchlauf:
def run_episode(
initial_state, actions, q_table, environment,
state_key_fn, epsilon=0.1, alpha=0.1, gamma=0.9,
max_steps=10, rng=None,
) -> tuple[list[Transition], float]:
transitions = []
cumulative_reward = 0.0
state = initial_state
for step in range(max_steps):
state_key = state_key_fn(state)
action_key, _ = epsilon_greedy(q_table, state_key, actions, epsilon, rng)
env_result = environment(state, action_key)
reward_signal = compute_reward(
env_result.test_pass_rate,
env_result.quality_score,
env_result.efficiency_score,
)
reward = shaped_reward(
env_result.test_pass_rate,
state.test_pass_rate,
reward_signal.total,
)
next_state = CodeState(
task_type=state.task_type,
complexity=state.complexity,
attempts=state.attempts + 1,
test_pass_rate=env_result.test_pass_rate,
)
q_table.update(state_key, action_key, reward,
state_key_fn(next_state), actions, alpha, gamma)
transitions.append(Transition(state, CodeAction(action_key, 1), reward, next_state))
cumulative_reward += reward
if env_result.is_terminal:
break
state = next_state
return transitions, round(cumulative_reward, 4)
Jede Episode liefert eine Liste von Transitions – die vollständige Aufzeichnung, welcher Zustand zu welcher Aktion führte und welches Feedback die Umgebung gab. Diese Transitions bilden die Grundlage für das Experience Replay (Artikel 04).
7. Von RL zu RLHF: Die Brücke zu LLMs
Reinforcement Learning from Human Feedback (RLHF) überträgt RL-Konzepte direkt auf die Optimierung von Sprachmodellen:
"RLHF combines reinforcement learning with human feedback to fine-tune language models. It aims to align the model's outputs with human preferences, improving the quality and safety of generated text." — Vaid & Lund, LLM Design Patterns, S. 452
| RL-Konzept | RLHF-Anwendung | Code-Agent-Analogie |
|---|---|---|
| Policy | Sprachmodell (Token-Generierung) | Code-Generierungsstrategie |
| Reward Model | Trainiert auf menschliche Präferenzen | Test-Framework + Linting + Metriken |
| Environment | Textgenerierungs-Kontext | Codebase + Test-Suite |
| Action Space | Alle möglichen Tokens | Strategien + Kontext-Level |
Der entscheidende Unterschied: Bei klassischem RLHF werden die Modellgewichte angepasst. Bei Code-Agenten bleibt das LLM unverändert – die „Policy" wird durch Prompt-Anpassung, Strategie-Selektion und Kontextanreicherung optimiert.
8. Fazit
Reinforcement Learning liefert das formale Gerüst für selbstlernende Code-Agenten:
- MDP modelliert die Interaktion als Zustand → Aktion → Reward → Folgezustand
- Reward Shaping transformiert binäres Pass/Fail in ein differenziertes Signal
- Q-Learning speichert und aktualisiert den Wert jeder Strategie pro Situation
- Epsilon-Greedy balanciert das Ausprobieren neuer Ansätze mit der Nutzung bewährter Strategien
Das LLM selbst wird nicht trainiert. Die „Lernfähigkeit" entsteht durch die RL-Architektur um das Modell herum: Q-Tabelle als Wissensspeicher, Epsilon-Decay als Reifeprozess, Reward Shaping als differenziertes Feedback-Signal.
Der nächste Artikel der Serie behandelt Automatisierte Tests als Reward-Signal – wie Test-Frameworks die „Umgebung" für Code-Agenten bilden und wie Reward Shaping in der Praxis gestaltet wird.
Quellen & Ressourcen
- Raschka, S. et al. (2022): Machine Learning with PyTorch and Scikit-Learn. Packt. S. 1025 ff. – Bellman-Gleichung, Q-Learning-Grundlagen, Policy-Konzepte.
- Jha, A. R. (2024): Mastering PyTorch. 2nd Edition. Packt. S. 393 ff. – Q-Funktion, Epsilon-Greedy, Q-Learning-Implementierung.
- Elgendy, M. (2024): Modern Computer Vision with PyTorch. 2nd Edition. Packt. S. 658 ff. – RL-Grundkonzepte, Exploration vs. Exploitation.
- Vaid, S. & Lund, B. (2025): LLM Design Patterns. Packt. S. 452 ff. – RLHF, Reward Models, Policy-Optimierung für Sprachmodelle.
- Christiano, P. et al. (2017): Deep Reinforcement Learning from Human Preferences. NeurIPS. – Grundstein für RLHF.
- Sutton, R. S. & Barto, A. G. (2018): Reinforcement Learning: An Introduction. 2nd Edition. MIT Press. – Standardwerk für RL-Theorie.
→ Vollständiger Code: src/