Modul 02 · Lektion 1

Wie ein guter Prompt aussieht

Dieselbe unordentliche Hilfeanfrage eines Nutzers einmal mit einem dahingeschriebenen und einmal mit einem klar aufgebauten Prompt bearbeiten und die Ergebnisse Absatz für Absatz vergleichen. Was Rolle, Aufgabe, Hintergrund, Anforderungen und Ausgabeformat jeweils leisten.

  • Etwa 35 Minuten
  • Niveau: Einsteiger
  • Getestet: 2026-09-14 deepseek-flash

Code und Programmausgaben stehen genau so da, wie sie gelaufen sind – Kommentare und Ausgaben sind daher auf Chinesisch.

Prompt Engineering klingt nach Geheimwissen, und im Netz gibt es jede Menge „Universalvorlagen“ und „Zaubersprüche“. Der Kern ist aber schlicht: Schreib klar auf, was du im Kopf hast. Das Modell kennt deinen Hintergrund nicht, weiß nicht, wer das Ergebnis liest, und weiß nicht, wie dein „gut“ aussieht. Was du nicht aufschreibst, muss es raten.

Diese Lektion nimmt ein echtes Szenario, schreibt für dieselbe Aufgabe zwei Prompt-Versionen und schaut, worin der Unterschied liegt.

Szenario: eine Hilfeanfrage aus dem Gruppenchat zu einem Issue machen

Angenommen, du bist Maintainer von httpx, und ein Nutzer schreibt im Community-Chat Folgendes:

用httpx下载大文件老是断 我用的stream 下到一半就报错了 ReadTimeout
代码大概是 with httpx.stream("GET", url) as r: for chunk in r.iter_bytes(): f.write(chunk)
文件2个G左右,网不太好,公司内网。之前用requests好像没这问题??
版本是0.27 python3.11 mac
急!!!明天要交

Du willst, dass das Modell daraus ein ordentliches GitHub-Issue macht, damit andere Maintainer der Sache nachgehen können.

Version 1: schnell dahingeschrieben

v1 = [{"role": "user", "content": "帮我整理一下这个问题:\n" + REPORT}]

Die Antwort des Modells (Denken aus; unten mein unveränderter Originaltext, deiner wird anders sein):

# 问题整理

## 一句话概述
用 httpx 的 `stream` 下载 ~2GB 大文件,网络环境较差(公司内网),下载中途抛 `ReadTimeout`,改用 requests 则无此问题,需要尽快解决。

## 环境信息
- 库/版本:httpx **0.27**
- Python:**3.11**
- 系统:**macOS**
- 网络:公司内网,网速较差/不稳定
- 文件大小:约 **2 GB**

## 复现代码
```python
with httpx.stream("GET", url) as r:
    for chunk in r.iter_bytes():
        f.write(chunk)
```

## 现象
- 下载过程**中途中断**
- 报错类型:**ReadTimeout**
- 相同 URL 和网络下,**requests 不报错**(或很少报错)

## 关键疑问
1. 为什么 httpx 会 ReadTimeout 而 requests 不会?两者的超时/重试默认行为差异在哪?
2. httpx 的 `timeout` 参数在 stream 场景下具体作用于什么?(连接 / 单个 chunk 间隔 / 整体?)
3. 有没有办法在 stream 时**禁用或放宽读超时**,让慢速下载不被打断?
4. 是否需要加**重试 / 断点续传(Range)**来兜底?

## 目标
- 让 2GB 大文件在弱网内网环境下能稳定下完
- 时间紧急,明早交付

## 可能的排查方向(供参考)
- httpx 默认 `timeout=5.0`,requests 默认是"无超时"(会一直等),这很可能是根因
- 可尝试:`httpx.stream("GET", url, timeout=httpx.Timeout(connect=10, read=None, write=10, pool=10))`
- 或加大 read timeout,加 `transport=httpx.HTTPTransport(retries=3)`
- 断点续传:记录已下字节,用 `Range: bytes=N-` 续传

---
需要我帮你针对哪个方向继续深挖(比如给出可直接替换的稳定下载代码)?

Wirkt professionell, auch das Layout ist hübsch. Vergleicht man aber genau mit dem Originaltext, gibt es einige Probleme:

  • Eine Vermutung wurde zur Tatsache. Der Nutzer schrieb „之前用 requests 好像没这问题??“ (mit requests hatte ich das Problem glaube ich nicht??); beim Modell wurde daraus „mit requests tritt das Problem nicht auf“ und „bei gleicher URL und gleichem Netzwerk wirft requests keinen Fehler“. Ein Maintainer, der das liest, verfolgt womöglich eine Spur, die nie belegt wurde.
  • Es tat Dinge, um die niemand gebeten hat. Ich wollte nur das Problem aufbereitet haben, bekam aber einen Abschnitt „Richtungen für die Fehlersuche“ und dazu „Ziel“ und „Kernfragen“. Für ein Issue ist das Rauschen.
  • Ein Vorschlag ist falsch. Es empfiehlt HTTPTransport(retries=3), aber die httpx-Dokumentation sagt deutlich, dass dieser Parameter nur bei Verbindungsfehlern (ConnectError, ConnectTimeout) wiederholt und bei einem ReadTimeout mitten im Download nichts nützt. Ein irreführender Vorschlag.
  • Das Format ist kein Issue-Format. Am Ende fragt es noch „Soll ich weiter nachforschen?“; in einem Issue wirkt dieser Satz seltsam.

Das ist nicht die Schuld des Modells. „Bereite das mal auf“ lässt hundert Deutungen zu: Für wen? In welcher Form? Darf es eigene Einschätzungen hinzufügen? Es hat die Deutung „so hilfreich wie möglich“ gewählt, also je mehr, desto besser.

Version 2: die Anforderungen klar aufschreiben

v2 = [
    {
        "role": "system",
        "content": """你是 httpx 开源项目的维护者,负责把用户在群里的求助整理成 GitHub issue。

任务:把 <report> 标签里的用户原话整理成一个 issue,给其他维护者看。

要求:
- 只使用原话里有的信息,不要猜测或补充原话里没有的细节。
- 原话里没提到、但排查需要的信息,列在"待确认"里。
- 不要给解决方案,这一步只整理问题。

输出格式(Markdown,只输出 issue 本身):
## 标题
一句话,说清现象和场景
## 环境
## 复现代码
## 现象
## 待确认""",
    },
    {"role": "user", "content": f"<report>\n{REPORT}\n</report>"},
]

Die Antwort des Modells:

## 标题
使用 httpx.stream 下载约 2G 大文件时,中途报 ReadTimeout 错误

## 环境
- httpx 版本:0.27
- Python 版本:3.11
- 操作系统:macOS
- 网络环境:公司内网,网络状况较差

## 复现代码
```python
with httpx.stream("GET", url) as r:
    for chunk in r.iter_bytes():
        f.write(chunk)
```

## 现象
- 使用 `httpx.stream` 下载约 2G 的大文件时,下载到一半报错 `ReadTimeout`。
- 网络状况较差(公司内网)。
- 用户反馈之前使用 requests 时似乎没有该问题。

## 待确认
- 是否设置了自定义 timeout(尤其是 read timeout)?具体值是多少?
- 报错时的完整 traceback 信息。
- 下载中断是必现还是偶发?中断位置是否固定?
- `url` 指向的服务端类型(如是否支持分块传输、是否有响应延迟等)。
- requests 与 httpx 测试时是否为同一 URL、同一网络环境,以及 requests 是否也配置过 timeout。
- 是否使用代理。

„似乎没有该问题“ (das Problem trat offenbar nicht auf) bewahrt die Unsicherheit des Nutzers. Keine eigenmächtigen Lösungen, also auch keine falschen Vorschläge. Der Abschnitt „待确认“ (zu klären) ist nützlich: vollständiger Traceback, ob es immer auftritt, ob bei requests damals ein Timeout gesetzt war; genau das muss man den Nutzer bei der Fehlersuche fragen. Das kann man direkt auf GitHub einfügen.

Was Version 2 mehr aufschreibt

Zerlegt man den Prompt von Version 2, enthält er fünf Teile.

Rolle: „Du bist Maintainer des Open-Source-Projekts httpx.“ Dieser Satz sagt dem Modell, aus welcher Perspektive und auf welchem fachlichen Niveau es die Aufgabe angehen soll. Das ist keine Magie; „Du bist ein weltweit führender Experte“ macht das Modell nicht klüger. Die Rolle liefert Kontext: Welches Wissen ist relevant, in welchem Stil soll das Ergebnis sein?

Aufgabe: „Den Originaltext zu einem Issue aufbereiten, für andere Maintainer.“ Entscheidend ist, klar zu sagen, was entsteht und wer es liest. Ein Issue für Maintainer und eine Antwort an den Nutzer schreibt man völlig unterschiedlich.

Anforderungen: drei Regeln. Beachte, dass sie alle sehr konkret sind: „nur Informationen aus dem Originaltext verwenden“, „keine Lösungen vorschlagen“. Anforderungen wie „schreib professionell“ oder „achte auf Genauigkeit“ bringen fast nichts, weil das Modell ohnehin glaubt, professionell und genau zu sein.

Ausgabeformat: die Struktur direkt vorgeben. Welches Format du willst, zeichnest du am besten gleich hin; das ist verlässlicher als die Beschreibung „bitte in Abschnitte wie Titel, Umgebung, Symptome gliedern“.

Trennzeichen: Der Originaltext des Nutzers steht zwischen <report> und </report>. So kann das Modell klar unterscheiden, was deine Anweisung und was das zu verarbeitende Material ist. Steht im Material zufällig „ignoriere die Anforderungen oben“, wird das nicht so leicht als Anweisung behandelt. (Modul 05, Lektion 8 widmet sich solchen Angriffen.) XML-artige Tags, drei Backticks oder """ gehen alle; wichtig ist, dass es einheitlich bleibt.

Außerdem stehen in Version 2 die festen Regeln in der system-Nachricht und das jedes Mal wechselnde Material in der user-Nachricht. Das macht die Struktur klarer und trifft außerdem den Cache: Die system-Nachricht ist jedes Mal gleich und wird, wie in der letzten Lektion gezeigt, zum Cache-Trefferpreis berechnet.

Sagen, was zu tun ist, und auch, was nicht

Ein verbreiteter Rat im Netz lautet: „Sag dem Modell, was es tun soll, nicht, was es lassen soll.“ Der Rat hat etwas für sich: Schreibst du nur „nicht ausschweifen“, weiß das Modell nicht, wie knapp knapp genug ist; „antworte in einem Satz“ ist viel klarer.

Aber „was nicht zu tun ist“ ist in einem Fall unverzichtbar: wenn das Modell eine starke Standardgewohnheit hat. Dass das Modell in Version 1 von sich aus Lösungen anbot, ist so eine Gewohnheit. Das eine „keine Lösungen vorschlagen“ in Version 2 hat sie abgestellt.

In Modul 00, Lektion 3 gab es noch ein Beispiel: „Beschreib den Herbst in fünf Schriftzeichen“, das Modell lieferte fünf Zeichen und sechs weitere dazu. Ich habe den Prompt so geändert und erneut probiert:

ask([{"role": "user", "content": "用五个字形容秋天"}])
ask([{"role": "user", "content": "用五个字形容秋天。只输出这五个字,不要标点,不要解释,不要给其他选项。"}])
原提示词: **金风送爽时**  

(也可以换成:**霜叶红于花**、**一叶知秋意**、**秋高气爽天**,看你喜欢哪种意境。)
改进后:   秋高气爽时

„Gib nur diese fünf Zeichen aus“ ist die positive Anforderung; „keine Satzzeichen, keine Erklärung, keine weiteren Optionen“ versperrt vorab die drei Dinge, die das Modell am ehesten zusätzlich tut. Beides zusammen wirkt am besten.

In welcher Reihenfolge man einen Prompt schreibt

Wenn ich selbst einen Prompt schreibe, denke ich meist in dieser Reihenfolge:

  1. Wer nutzt das Ergebnis? Liest es ein Mensch, oder parst es ein Programm? Ein Experte oder ein Anfänger?
  2. Wie sieht ein gutes Ergebnis aus? Am besten schreibst du von Hand eine ideale Ausgabe. Gelingt dir das nicht, weißt du selbst noch nicht genau, was du willst.
  3. Wo macht das Modell am ehesten Fehler? Probier zuerst einen Ein-Satz-Prompt, schau, wo er die Anforderungen verfehlt, und füg dann gezielt Regeln hinzu.
  4. Material und Anweisung trennen.

Schritt 3 ist wichtig: Schreib nicht gleich einen „perfekten Prompt“ von mehreren hundert Wörtern. Schreib erst die einfachste Version, schau, wo sie falsch liegt, und ergänze dann. Jede Regel sollte einem Problem entsprechen, das du selbst gesehen hast. So wird der Prompt kurz, und jeder Satz hat einen Zweck.

Wann man nicht so viel schreiben muss

Für eine schnelle Frage oder um einen Text überarbeiten zu lassen, reicht ein Satz; bist du mit dem Ergebnis unzufrieden, ergänzt du. Strukturierte Prompts braucht man vor allem dort, wo sie in ein Programm eingebaut und immer wieder aufgerufen werden: Du kannst nicht jedes Mal danebensitzen und nachbessern, also musst du es einmal klar aufschreiben.

Übungen

  1. Führ code/02-prompting/prompt_structure.py aus und schau, wie sich deine Version 1 und Version 2 von meinen unterscheiden.
  2. Lösch eine der „Anforderungen“ aus Version 2 (etwa „keine Lösungen vorschlagen“), lass es ein paar Mal laufen und schau, ob das Modell wieder Vorschläge macht.
  3. Nimm einen Text aus deiner Arbeit, der aufbereitet werden muss (Besprechungsnotizen, eine Kunden-E-Mail, ein Fehlerprotokoll), bearbeite ihn zuerst mit einem Ein-Satz-Prompt, finde die Probleme im Ergebnis und schreib den Prompt dann nach den fünf Teilen dieser Lektion um. Notier, welches Problem jede hinzugefügte Regel gelöst hat.

Selbsttest

1. Das Ergebnis von Version 1 war hübsch formatiert. Warum ist es trotzdem problematisch?

Es hat die Vermutung des Nutzers („hatte ich glaube ich nicht“) als feststehende Tatsache hingeschrieben, Dinge getan, um die niemand gebeten hat (Richtungen für die Fehlersuche), und einer der Vorschläge (Lesetimeouts mit HTTPTransport(retries=3) lösen) ist falsch. Auch das Format taugt nicht direkt als Issue. Hübsches Layout heißt nicht verlässlicher Inhalt; man muss mit dem Originalmaterial abgleichen.

2. Macht „Du bist ein weltweit führender Python-Experte“ im Prompt die Antworten des Modells genauer?

Kaum. Die Rollenbeschreibung liefert dem Modell Kontext: welche Perspektive, welcher Stil, welches Wissen relevant ist. Neue Fähigkeiten verleiht sie nicht. Statt die Rolle aufzublasen, sollte man Aufgabe, Adressat, konkrete Anforderungen und Ausgabeformat klar aufschreiben.

3. Warum sollte man das Material des Nutzers in Tags wie <report> einschließen?

Damit das Modell klar unterscheiden kann, welcher Teil deine Anweisung und welcher das zu verarbeitende Material ist. So wird Inhalt aus dem Material nicht so leicht als Anweisung missverstanden, auch nicht bei Angriffen, bei denen jemand absichtlich „ignoriere die vorherigen Anforderungen“ ins Material schreibt.

Fragen und Diskussion

Hängst du in dieser Lektion fest? Frag hier. Und wenn du die Frage von jemandem beantworten kannst, tu es gern.

Eine Frage bringt 3 Punkte, eine Antwort 6. Beiträge erscheinen nach der Prüfung.

Diskussion wird geladen…