Der erste Aufruf eines Sprachmodells
Ein Programm von gut zehn Zeilen schreiben, das DeepSeek aufruft, und Anfrage und Antwort Feld für Feld verstehen – Nachrichtenrollen, Ende-Grund, Token-Verbrauch und Kosten – und dann mit curl sehen, dass es im Kern nur eine HTTP-Anfrage ist.
- Etwa 30 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.
Eine kurze Suche im Netz liefert Beispielcode für den Aufruf eines Sprachmodells; kopieren, die Frage ändern, und es läuft. Viele bleiben genau dort stehen: Das Programm läuft, aber was in dem großen zurückgegebenen Objekt steckt, wissen sie nicht. Stoßen sie später auf Fragen wie „Warum ist die Antwort plötzlich nur ein halber Satz?“ oder „Warum ist die Rechnung diesen Monat so hoch?“, wissen sie überhaupt nicht, wo sie suchen sollen.
Diese Lektion schreibt nur ein sehr kurzes Programm, erklärt aber jedes Feld in Anfrage und Antwort.
Der kleinste mögliche Aufruf
Leg im Verzeichnis ai-course aus der letzten Lektion eine neue Datei first_call.py an:
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["LLM_API_KEY"],
base_url=os.environ.get("LLM_BASE_URL", "https://api.deepseek.com"),
)
MODEL = os.environ.get("LLM_MODEL", "deepseek-flash")
response = client.chat.completions.create(
model=MODEL,
messages=[
{"role": "system", "content": "你是一个说话简短的助手,每次回答不超过两句话。"},
{"role": "user", "content": "Python 里的列表和元组有什么区别?"},
],
)
message = response.choices[0].message
print("回答:", message.content)
print("结束原因:", response.choices[0].finish_reason)
print("实际使用的模型:", response.model)
print("输入词元:", response.usage.prompt_tokens)
print("输出词元:", response.usage.completion_tokens)
# DeepSeek 的模型默认先思考再回答,思考过程放在 reasoning_content 里。
# 别的服务商没有这个字段,所以用 getattr 取,取不到就是 None。
reasoning = getattr(message, "reasoning_content", None)
if reasoning:
print("思考过程(前 100 字):", reasoning[:100])
Ausführen:
uv run python first_call.py
Mein Ergebnis:
回答: 列表可变、用 `[]`,元组不可变、用 `()`。因此列表适合频繁修改的数据,元组更适合固定不变的数据。
结束原因: stop
实际使用的模型: deepseek-flash
输入词元: 52
输出词元: 145
思考过程(前 100 字): 我们需要回答中文。用户要求:你是一个说话简短的助手,每次回答不超过两句话。问题:Python 里的列表和元组有什么区别?需要不超过两句话。要准确。可以一句或两句。核心区别:列表可变,用方括号;元组不可
Deine Antwort wird anders formuliert sein, und auch die Token-Zahlen weichen etwas ab; das ist normal.
Das Programm hat nur drei Schritte: einen Client erzeugen, chat.completions.create aufrufen, Dinge aus dem Rückgabewert holen. Schauen wir sie einzeln an.
Der Client
OpenAI(...) erzeugt ein Client-Objekt. Es schickt deine Anfragen an den Server unter base_url und fügt den api_key in den Anfrage-Header ein. Die Klasse heißt OpenAI, kann sich aber mit jedem Dienst mit OpenAI-kompatibler Schnittstelle verbinden. Zeigt base_url auf DeepSeek, spricht er mit DeepSeek.
Der Name chat.completions stammt von der „Chat Completion“-Schnittstelle, die OpenAI ursprünglich entworfen hat. Sie ist zum faktischen Branchenstandard geworden, und die meisten Modelldienste, in China wie anderswo, bieten eine Schnittstelle im selben Format an. Wer das einmal gelernt hat, kommt also mit fast jedem Anbieter zurecht.
Die Anfrage: model und messages
Die Anfrage hat nur zwei Pflichtparameter.
model ist der Modellname. Der Server entscheidet danach, welches Modell antwortet.
messages ist eine Liste, deren Elemente jeweils eine Nachricht mit den Feldern role (Rolle) und content (Inhalt) sind. Es gibt drei Rollen:
| Rolle | Wer spricht | Wofür |
|---|---|---|
system |
der Entwickler | dem Modell Regeln geben: welche Rolle es spielt, welcher Ton, welche Einschränkungen |
user |
der Nutzer | Frage oder Anweisung des Nutzers |
assistant |
das Modell | frühere Antworten des Modells. In mehrstufigen Gesprächen gibt man ihm zurück, was es vorher gesagt hat |
Im Beispiel oben verlangt die system-Nachricht „jede Antwort höchstens zwei Sätze“, und das Modell hat tatsächlich mit zwei Sätzen geantwortet. Der Nutzer sieht die system-Nachricht nicht, aber sie beeinflusst jede Antwort des Modells. Beim Bau einer Anwendung stehen „Persona“ und Regeln des Produkts im Wesentlichen hier.
Die Rolle assistant brauchen wir in dieser Lektion noch nicht. Vielleicht denkst du, das Modell erinnert sich an deine letzte Frage; das tut es nicht, jeder Aufruf ist unabhängig. Soll es sich das vorige Gespräch „merken“, musst du die bisherigen Fragen und Antworten als user- und assistant-Nachrichten einzeln in messages packen und erneut senden. Modul 03, Lektion 1 widmet sich genau dem.
Die Antwort: choices, finish_reason, usage
Im zurückgegebenen response-Objekt braucht man am häufigsten diese Dinge:
response.choices[0].message.content: die Antwort des Modells. choices ist eine Liste, weil die Schnittstelle mehrere Antwortkandidaten auf einmal erlaubt; meist gibt es aber nur einen, also nimmt man direkt den nullten.
response.choices[0].finish_reason: warum das Modell aufgehört hat. Übliche Werte:
| Wert | Bedeutung |
|---|---|
stop |
das Modell hält sich für fertig, normales Ende |
length |
die Längengrenze wurde erreicht und die Antwort abgeschnitten. Sie ist sehr wahrscheinlich unvollständig |
tool_calls |
das Modell will ein Tool aufrufen (Modul 03, Lektion 3) |
content_filter |
der Sicherheitsfilter des Anbieters hat den Inhalt blockiert |
Im Programm musst du das prüfen. Ist es length, hast du womöglich einen halben Satz bekommen; zeigst du ihn direkt dem Nutzer oder parst ihn als JSON, gibt es Probleme.
response.model: das Modell, das tatsächlich geantwortet hat. Meist ist es das angefragte, aber es gibt Ausnahmen: Alte Modellnamen werden vom Anbieter manchmal auf neue Modelle umgeleitet. Stand September 2026 beantwortet zum Beispiel eine Anfrage an den alten Namen deepseek-chat in Wirklichkeit deepseek-flash im Nicht-Denkmodus. Bei der Fehlersuche ist dieses Feld daher verlässlicher als der Modellname, den du selbst geschrieben hast.
response.usage: wie viele Tokens der Aufruf verbraucht hat. prompt_tokens ist die Eingabe, completion_tokens die Ausgabe. Tokens sind die Grundeinheit, in der ein Modell Text verarbeitet: ein Schriftzeichen, ein halbes englisches Wort oder eine Kombination aus mehreren Zeichen. In wie viele Tokens ein Text zerfällt, ist bei jedem Modell anders; Lektion 1 des nächsten Moduls zerlegt das praktisch. Anbieter rechnen nach Tokens ab, usage ist also deine Rechnung.
Was hat dieser Aufruf gekostet
Stand September 2026 kostet deepseek-flash (US-Dollar pro einer Million Tokens):
| Hauptzeit | Nebenzeit | |
|---|---|---|
| Eingabe (Cache-Fehlschlag) | 0,30 | 0,15 |
| Eingabe (Cache-Treffer) | 0,006 | 0,003 |
| Ausgabe | 1,20 | 0,60 |
Hauptzeit ist montags bis freitags 01:00–04:00 und 06:00–10:00 UTC, also werktags 9:00–12:00 und 14:00–18:00 Pekinger Zeit; zu allen anderen Zeiten gilt der Nebenzeitpreis zum halben Preis. „Cache-Treffer“ heißt, dass der Anfang dieser Eingabe mit einer früheren Anfrage übereinstimmt und der Server Berechnungen wiederverwenden kann; Modul 06 zeigt, wie man damit spart.
Zum Hauptzeitpreis kostete der Aufruf oben:
input_cost = 52 * 0.30 / 1_000_000
output_cost = 145 * 1.20 / 1_000_000
print(f"{input_cost + output_cost:.6f} 美元")
0.000190 美元
Ein Dollar reicht für über fünftausend solcher Fragen. Das wirkt billig, aber achte auf zwei Dinge. Erstens ist die Ausgabe viermal so teuer wie die Eingabe; das Modell weniger schwafeln zu lassen, spart also Geld. Zweitens wächst diese Zahl schnell, wenn dein Programm bei jedem Aufruf ein ganzes Dokument in die Eingabe stopft und zehntausende Male am Tag aufgerufen wird.
Woher kommen die 145 Ausgabe-Tokens
Die Antwort besteht nur aus zwei Sätzen, etwa vierzig Schriftzeichen, trotzdem sind es 145 Ausgabe-Tokens. Der Rest ist der Denkprozess.
deepseek-flash hat standardmäßig den Denkmodus eingeschaltet: Es denkt zuerst in reasoning_content nach und gibt dann in content die eigentliche Antwort. In der Ausgabe oben siehst du sein Denken: Es wiederholt erst die Vorgabe „höchstens zwei Sätze“ und formuliert dann die Antwort. Die Tokens des Denkens zählen zu completion_tokens und werden zum Ausgabepreis berechnet. Ich habe das Programm dreimal hintereinander laufen lassen; das Denken verbrauchte 137, 110 und 189 Tokens, ein Mehrfaches der eigentlichen Antwort.
Denken macht das Modell bei komplexen Fragen genauer (Modul 02, Lektion 3 macht einen Vergleich), bei einfachen Fragen verschwendet es aber nur Geld und Zeit. DeepSeek erlaubt, es auszuschalten:
response = client.chat.completions.create(
model=MODEL,
messages=[...],
extra_body={"thinking": {"type": "disabled"}},
)
extra_body ist ein Durchgang, den das OpenAI-SDK für anbieterspezifische Parameter vorsieht. thinking ist ein Parameter von DeepSeek, den andere Anbieter vielleicht nicht kennen. Beim Anbieterwechsel musst du ihn entfernen oder nachsehen, mit welchem Parameter der andere das Denken steuert.
Auch die Eingabe-Tokens haben ein Detail. Bei denselben beiden Nachrichten (zusammen 43 Schriftzeichen) waren es im Nicht-Denkmodus 27 prompt_tokens, mit Denkmodus 52. Der Nachrichteninhalt ist gleich; die 25 zusätzlichen Tokens sind Formatmarkierungen, die der Server hinzufügt: Er markiert um die Nachrichten herum, was system und was user ist und wo das Denken beginnt, und gibt das dann ans Modell; diese Markierungen zählen ebenfalls als Eingabe-Tokens. Dass 43 Zeichen nur gut zwanzig Tokens ergeben, zeigt, dass der Tokenizer von DeepSeek oft zwei oder drei chinesische Schriftzeichen zu einem Token zusammenfasst; Lektion 1 des nächsten Moduls schaut sich das genauer an.
Eine Falle: Das Denken verbraucht das Kontingent
Mit dem Parameter max_tokens legst du fest, wie viele Tokens höchstens ausgegeben werden; man nutzt ihn oft, um Kosten zu begrenzen und endloses Weiterschreiben zu verhindern. Im Denkmodus verbraucht aber auch das Denken dieses Kontingent.
Ich habe max_tokens auf 30 gesetzt, gefragt „Stell die List Comprehensions von Python vor“ und je einmal mit ausgeschaltetem und mit eingeschaltetem Denken ausgeführt:
== 非思考: finish_reason=length completion_tokens=30 reasoning_tokens=None
content: '## Python 列表推导式(List Comprehension)\n\n列表推导式是 Python 中一种**简洁优雅**的创建列表的方式,可以用一行代码'
reasoning: ''
== 思考: finish_reason=length completion_tokens=30 reasoning_tokens=30
content: ''
reasoning: 'We need answer in Chinese. User asks "介绍一下 Python 的列表推导式。" Need introduce Python'
Im Nicht-Denkmodus wurde die Antwort mitten im Satz abgeschnitten, wie erwartet. Im Denkmodus gingen alle 30 Tokens ins Denken, und die eigentliche Antwort content ist ein leerer String. Schaut dein Programm nur auf content, glaubt es, das Modell habe gar nichts gesagt.
Daraus zwei Lehren: Mit eingeschaltetem Denken max_tokens großzügig bemessen; im Programm unbedingt finish_reason prüfen und bei length wissen, dass das Ergebnis unvollständig ist.
Mit curl sehen, wie es wirklich aussieht
Was das SDK für dich tut, ist im Grunde, eine HTTP-Anfrage zu senden. Ohne Python geht das auch mit curl auf der Kommandozeile:
curl https://api.deepseek.com/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $LLM_API_KEY" \
-d '{
"model": "deepseek-flash",
"messages": [{"role": "user", "content": "用五个字形容秋天"}],
"thinking": {"type": "disabled"}
}'
Die Windows-PowerShell behandelt Anführungszeichen anders, daher klappt dieser Befehl dort vielleicht nicht. Du kannst ihn in Git Bash oder WSL ausführen oder diesen Schritt überspringen; für den weiteren Kurs macht das nichts.
Zurück kommt JSON, das ich formatiert habe:
{
"id": "10bef209-3437-4efc-906b-dcb18fe30f7f",
"object": "chat.completion",
"created": 1789444049,
"model": "deepseek-flash",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "**金风送爽凉**\n\n如果不局限于这五个字,还有其他不同角度的五字形容:\n\n- **秋高气爽天** — 天高云淡,气候宜人\n- **霜叶红于花** — 枫叶经霜比花还红\n- **硕果满枝头** — 丰收的景象\n- **一叶知秋来** — 落叶预示着秋天到来\n- **寒蝉鸣凄切** — 秋蝉叫声悲凉\n- **天凉好个秋** — 辛弃疾词句,凉爽舒适"
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 9,
"completion_tokens": 121,
"total_tokens": 130,
"prompt_tokens_details": {
"cached_tokens": 0
},
"prompt_cache_hit_tokens": 0,
"prompt_cache_miss_tokens": 9
},
"system_fingerprint": "aeb56401ca74e127821c4f9126dcb669"
}
Die Felder entsprechen eins zu eins denen in Python: choices[0].message.content, finish_reason, usage. Das SDK verwandelt dieses JSON nur in Python-Objekte und kümmert sich nebenbei um Wiederholungen, Timeouts und dergleichen. Wer das verstanden hat, kann ein Sprachmodell aus jeder Sprache aufrufen und bei Problemen direkt mit curl prüfen, ob es an deinem Code oder am Server liegt.
Beachte, dass thinking in curl direkt auf der obersten Ebene des JSON steht. Übergibst du es in Python per extra_body, fügt das SDK es am Ende auch in dieses JSON ein.
Noch ein Detail lohnt einen Blick: Ich wollte „Beschreib den Herbst in fünf Schriftzeichen“, das Modell lieferte fünf Zeichen und fügte eigenmächtig sechs weitere Varianten hinzu. Modelle sagen oft mehr als verlangt; darum kümmert sich Modul 02 beim Thema Prompts.
Häufige Probleme
content ist None oder ein leerer String: Schau zuerst auf finish_reason. Ist es length, war max_tokens zu klein und wurde vom Denken aufgebraucht. Ist es tool_calls, will das Modell ein Tool aufrufen, und die Antwort steht in einem anderen Feld.
Fehler 429: zu viele Anfragen, Rate Limit. Ein paar Sekunden warten und erneut versuchen. Modul 03, Lektion 4 zeigt automatische Wiederholungen.
Das Programm hängt lange ohne Reaktion: Im Denkmodus kann das Nachdenken über eine komplexe Frage mehrere Dutzend Sekunden dauern. Probier zuerst eine einfache Frage, um sicherzugehen, dass das Programm selbst funktioniert. Modul 03, Lektion 2 behandelt Streaming, bei dem die Antwort beim Erzeugen angezeigt wird.
Übungen
- Ändere die
system-Nachricht in „Du bist ein alter Gelehrter, der nur in klassischem Chinesisch antwortet“, stell dieselbe Frage noch einmal und schau, wie sich die Antwort verändert. - Füg dem Aufruf
extra_body={"thinking": {"type": "disabled"}}hinzu und vergleichecompletion_tokensund Laufzeit vor und nach dem Ausschalten des Denkens. - Angenommen, deine Anwendung wird täglich 10.000-mal aufgerufen, jeweils mit 2000 Eingabe- und 500 Ausgabe-Tokens (Denken aus), alles zum Hauptzeitpreis. Was kostet ein Monat (30 Tage)? Rechne es mit Python aus.
- Füg in
messagesvon Hand zwei Nachrichten ein, die ein bereits geführtes Gespräch simulieren: Zuerst fragt user „Ich heiße Xiao Wang, merk dir das“, dann antwortet assistant „Gut, Xiao Wang“, und zum Schluss fragt user „Wie heiße ich?“. Kann das Modell richtig antworten? Überleg, warum.
Selbsttest
1. Was bedeutet finish_reason gleich length? Wie sollte das Programm damit umgehen?
Die Ausgabe des Modells hat die Grenze max_tokens erreicht und wurde abgeschnitten; die Antwort ist sehr wahrscheinlich unvollständig. Das Programm darf sie nicht wie ein normales Ergebnis verwenden: Man kann mit höherem max_tokens neu anfragen oder den Nutzer zumindest darauf hinweisen. Sollte die Antwort als JSON geparst werden, schlägt das bei abgeschnittenem JSON garantiert fehl.
2. Mit eingeschaltetem Denkmodus und max_tokens = 50 ist content leer. Warum?
Auch das Denken verbraucht das Kontingent von max_tokens. Alle 50 Tokens gingen ins Denken, und die Grenze war erreicht, bevor die eigentliche Antwort begann. Mit eingeschaltetem Denken muss man max_tokens großzügig wählen oder bei einfachen Aufgaben das Denken ausschalten.
3. Du hast deepseek-chat angefragt, aber response.model zeigt deepseek-flash. Ist das normal?
Ja. Anbieter leiten alte Modellnamen auf neue Modelle um und bieten sie so weiter an. response.model sagt dir, welches Modell tatsächlich geantwortet hat; bei Fehlersuche und Rechnungsprüfung zählt dieses Feld.
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…