Track
Duże modele językowe coraz częściej ujawniają, jaky myślą. Z DeepSeek V4's trzem explicytnymi trybami rozumowania, Non-think, Think High i Think Max, można teraz kontrolować głębokość rozumowania na poziomie API, a właściwy wybór zależy całkowicie od zadania.
W tym samouczku pokażę, jak zbudować w Streamlit arenę trybów myślenia DeepSeek V4. Aplikacja uruchamia równolegle ten sam prompt we wszystkich trzech trybach i prezentuje porównanie odpowiedzi obok siebie wraz z opóźnieniem, wykorzystaniem tokenów, kosztem i głębokością śladu rozumowania. Po ukończeniu tego samouczka będziesz mieć działające narzędzie, które potrafi:
-
Uruchamiać jednocześnie wszystkie trzy tryby rozumowania DeepSeek V4, korzystając z równoległych wywołań API
-
Parsować i wyświetlać zwijane ślady myślenia z bloków
<think>...</think> -
Śledzić opóźnienie, tokeny wyjściowe, przepustowość i szacowany koszt dla każdego trybu
-
Wskazywać zwycięzcę w czterech wymiarach: najszybszy, najtańszy, najbardziej efektywny i najwyżej oceniany przez człowieka
Pełny kod do tego samouczka jest dostępny w moim repozytorium na GitHubie.
Czym jest DeepSeek V4?
DeepSeek V4 to najnowsza generacja modeli językowych Mixture-of-Experts (MoE) od DeepSeek AI. Seria obejmuje dwie wersje:
- DeepSeek-V4-Pro: Łącznie 1,6T parametrów (49B aktywowanych) i obsługa kontekstu o długości 1 M tokenów.
- DeepSeek-V4-Flash: Mniejsza wersja modelu ma łącznie 284B parametrów (13B aktywowanych), również z oknem kontekstu 1M tokenów.
Kilka usprawnień architektonicznych odróżnia V4 od poprzedników:

Rysunek: Architektura serii DeepSeek V4 (Raport techniczny DeepSeek)
- Hybrydowa architektura uwagi (CSA / HCA): Prawa ścieżka (na powyższym rysunku) w każdym bloku zaczyna się od Compressed Sparse Attention (CSA) i Heavily Compressed Attention (HCA). Zamiast uwagi na każdym tokenie w każdej warstwie, mechanizmy te selektywnie kompresują obliczenia uwagi. W kontekście 1M tokenów oznacza to, że DeepSeek-V4-Pro potrzebuje jedynie 27% FLOPs inferencji dla pojedynczego tokena i 10% pamięci KV w porównaniu z DeepSeek-V3.2. Wyjście uwagi jest kierowane przez Pre-Block i Post-Block Mixing, po czym dodawane z powrotem do głównego stanu ukrytego poprzez połączenie rezydualne.
- DeepSeekMoE: Po uwadze ta sama ścieżka trafia do warstwy DeepSeekMoE, gdzie komponent Mixture-of-Experts aktywuje tylko podzbiór parametrów na token. Dzięki temu 284B parametrów V4-Flash zachowuje się podczas inferencji jak model 13B, a 1,6T parametrów V4-Pro — jak model 49B.
- Manifold-Constrained Hyper-Connections (mHC): Lewa ścieżka (na powyższym rysunku) pokazuje zstackowane reprezentacje stanu ukrytego przepływające przez Residual Mixing u dołu i u góry każdego bloku. To są warstwy mHC, gdzie zamiennik standardowych połączeń rezydualnych ogranicza aktualizacje do leżących na wyuczonym rozmaitości, poprawiając stabilność sygnału gradientu w bardzo głębokich stosach bez utraty ekspresyjności.
- Optymalizator Muon: Muon (niewidoczny na diagramie architektury) zastępuje AdamW dla większości parametrów regułą aktualizacji inspirowaną metodami drugiego rzędu, co zapewnia szybszą zbieżność i stabilniejsze przebiegi funkcji straty w skalach, na których trenowano V4.
Oba modele obsługują trzy odrębne tryby wysiłku rozumowania: Non-think, Think High i Think Max, które omówimy i porównamy w tym samouczku.
Wyniki benchmarków DeepSeek V4
Zespół DeepSeek porównał wydajność modelu V4 z odpowiednikami i oto wnioski:
Rysunek: Wydajność benchmarkowa serii DeepSeek V4 (Raport techniczny DeepSeek)
Wykres słupkowy po lewej pokazuje, gdzie V4-Pro-Max plasuje się względem czołowych zamkniętych modeli w zadaniach wiedzy, rozumowania i agentowych. Dwa wykresy po prawej tłumaczą, dlaczego zyski efektywności mają znaczenie — i to są bardziej zaskakujące wyniki. DeepSeek-V4-Pro-Max (tryb rozumowania Think Max) osiąga konkurencyjne wyniki względem czołowych zamkniętych modeli:
|
Benchmark |
DeepSeek-V4-Pro Max |
Claude Opus 4.6 Max |
Gemini-3.1-Pro High |
|
LiveCodeBench (Pass@1) |
93.5 |
88.8 |
91.7 |
|
Ocena Codeforces |
3206 |
— |
3052 |
|
GPQA Diamond (Pass@1) |
90.1 |
91.3 |
94.3 |
|
SWE Verified (Resolved) |
80.6 |
80.8 |
80.6 |
Ocena Codeforces 3206 lokuje V4-Pro-Max na poziomie Legendary Grandmaster, dzięki czemu zadania kodowania na naszej arenie stanowią naprawdę ciekawy test wydolności.
Zrozumienie trzech trybów myślenia
Zanim zbudujemy aplikację, warto zrozumieć, co robi każdy tryb:
Non-think: Model odpowiada bezpośrednio, bez wewnętrznego łańcucha rozumowania. Ten tryb jest idealny tam, gdzie liczy się szybkość bardziej niż głębia, np. konwersja formatów, proste wyszukiwania i jednokzdaniowe podsumowania.Think High: Model rozumuje krok po kroku, zanim zadeklaruje odpowiedź. To dobry kompromis między głębią a kosztem dla umiarkowanie złożonych zadań, takich jak debugowanie, projektowanie systemów i planowanie.Think Max: Model maksymalnie rozwija rozumowanie, bada wiele podejść, rygorystycznie weryfikuje logikę i deklaruje odpowiedź dopiero, gdy ma pewność. Dlatego trybThink Maxnajlepiej sprawdza się przy trudnych dowodach matematycznych, złożonych refaktoryzacjach wieloplikowych lub wszędzie tam, gdzie błędna pewna odpowiedź jest gorsza niż wolniejsza, ale zweryfikowana.
Kluczowy wniosek z tego samouczka jest taki, że Think Max nie zawsze wygrywa. Przy trywialnych zadaniach marnuje zasoby obliczeniowe przy identycznej jakości. Celem areny jest pokazanie, który tryb rzeczywiście warto stosować dla danego rodzaju zadania, na podstawie realnego opóźnienia, liczby tokenów, kosztu i ocen użytkowników.
Jeśli interesuje Pana/Panią porównanie modelu z innymi najnowocześniejszymi LLM-ami, polecam nasze artykuły: Claude Opus 4.7 vs DeepSeek V4 oraz GPT-5.5 vs DeepSeek V4.
Samouczek: budowa areny trybów myślenia z DeepSeek V4
W tym samouczku zbudujemy aplikację Streamlit, która:
-
Przyjmuje od użytkownika prompt i kategorię zadania
-
Uruchamia trzy równoległe wywołania API, po jednym na tryb rozumowania, korzystając z
ThreadPoolExecutorw Pythonie -
Parsuje ślady myślenia z bloków
<think>...</think>w wyjściu modelu -
Śledzi opóźnienie, liczbę tokenów i szacowany koszt dla każdego trybu
-
Prezentuje porównanie obok siebie z metrykami, systemem ocen użytkownika i podsumowaniem zwycięzcy
Pełna działająca aplikacja to pojedynczy plik app.py bez potrzeby zewnętrznych baz danych czy usług w tle.
Pełny kod do tego samouczka jest dostępny w moim repozytorium na GitHubie.
Krok 1: Konfiguracja projektu i zależności
Zacznij od utworzenia folderu projektu i zainstalowania dwóch wymaganych pakietów:
mkdir deepseek-arena && cd deepseek-arena
pip install streamlit>=1.32.0 openai>=1.12.0
openai wykorzystujemy, ponieważ API DeepSeek jest kompatybilne z OpenAI, co oznacza, że możemy skierować klienta OpenAI na bazowy URL DeepSeek bez innych zmian. Nie jest potrzebny żaden dedykowany SDK DeepSeek.
Pobrać klucz API z platform.deepseek.com i ustawić go jako zmienną środowiskową:
export DEEPSEEK_API_KEY="sk-..."
Skoro środowisko jest gotowe, przechodzimy do konfiguracji modeli i ich trybów.
Krok 2: Konfiguracja — modele, tryby i ceny
Zanim napiszemy logikę, definiujemy importy, identyfikatory modeli, konfiguracje trybów rozumowania i stałe cenowe. Te słowniki kontrolują działanie całej aplikacji — od listy modeli w rozwijanym menu, przez parametry API sterujące każdym trybem rozumowania, po schemat kolorów UI i szacunki kosztów wyświetlane w panelu porównawczym.
import os
import time
import concurrent.futures
from dataclasses import dataclass
from typing import Optional, Dict
import streamlit as st
from openai import OpenAI
DEEPSEEK_BASE_URL = "https://api.deepseek.com/"
DEEPSEEK_API_KEY_ENV = "DEEPSEEK_API_KEY"
MODELS = {
"DeepSeek-V4-Flash (default)": "deepseek-v4-flash",
"DeepSeek-V4-Pro": "deepseek-v4-pro",
}
MODES: Dict[str, dict] = {
"Non-think": {
"icon": "",
"color": "#10b981",
"badge": "green",
"desc": "Fast, direct answers — no internal reasoning",
"thinking_type": "disabled",
"reasoning_effort": None,
},
"Think High": {
"icon": "",
"color": "#3b82f6",
"badge": "blue",
"desc": "Careful step-by-step reasoning before responding",
"thinking_type": "enabled",
"reasoning_effort": "high",
},
"Think Max": {
"icon": "",
"color": "#ef4444",
"badge": "red",
"desc": "Exhaustive reasoning — push analysis to the limit",
"thinking_type": "enabled",
"reasoning_effort": "max",
},
}
PRICING = {
"deepseek-v4-flash": {
"input_cache_hit": 0.0028,
"input_cache_miss": 0.14,
"output": 0.28,
},
"deepseek-v4-pro": {
"input_cache_hit": 0.003625,
"input_cache_miss": 0.435,
"output": 0.87,
},
}
Warto rozwinąć kilka decyzji projektowych:
-
Parametry trybu myślenia: Każdy wpis w
MODESniesie dwa pola API zamiast promptu systemowego:thinking_typeorazreasoning_effort(„high”,"max"lubNone).thinking_typeprzekazujemy przezextra_body={"thinking": {"type": ...}}, areasoning_effortto parametr na najwyższym poziomie żądania. Non-think ustawiathinking_typena "disabled"i pozostawiareasoning_effortjakoNone, więc żadne ciało „thinking” nie jest w ogóle wysyłane. -
Flash jako model domyślny: Flash aktywuje 13B parametrów na token wobec 49B w Pro, dzięki czemu trzy równoległe wywołania są na tyle tanie, że można je wielokrotnie uruchamiać podczas eksperymentów. Pro jest dostępny z listy, gdy potrzebna jest wydajność z najwyższej półki, ale
Think Maxna Pro szybko zużywa limity, więc pozostawienie go jako opcji zamiast domyślnego to celowe zabezpieczenie kosztowe.
Uwaga: Wartości użyte w powyższym kodzie odzwierciedlają V4-Pro przy obecnej promocji. Zawsze weryfikuj bieżące stawki na api-docs.deepseek.com/quick_start/pricing przed publikowaniem porównań kosztów, ponieważ zarówno stawki bazowe, jak i promocje często się zmieniają po wydaniu nowego modelu.
Krok 3: Szablony zadań
Projekt zadań to najbardziej niedoceniany element budowy porównania rozumowania. Jeśli wszystkie zadania są trudne i otwarte, Think Max zawsze wygra — i demo niczego nie uczy. Zestaw zadań musi obejmować pełne spektrum trudności, aby każdy tryb miał przynajmniej jedną kategorię, w której wygrywa.
TASKS = {
"Trivial / Lookup": {
"prompt": (
"Complete both tasks below:\n\n"
"Task A — Convert this JSON to YAML:\n"
'{"name": "Alice", "age": 30, "skills": ["Python", "ML", "LLMs"],\n'
' "address": {"city": "San Francisco", "zip": "94105"}}\n\n'
"Task B — Summarize this paragraph in exactly one sentence:\n"
'"Large language models have rapidly transformed natural language processing '
"by demonstrating unprecedented capabilities across translation, summarization, "
"reasoning, and code generation, driven by scale and alignment techniques like RLHF "
'that bring model outputs closer to human intent."'
),
"expected_winner": "Non-think",
"tip": "Non-think should dominate here. No reasoning is required.",
},
"Coding / Debugging": {
"prompt": (
"Find every bug in the Python code below, explain each bug clearly, "
"and provide a fully corrected version:\n\n"
"```python\n"
"def binary_search(arr, target):\n"
" left, right = 0, len(arr)\n"
" while left < right:\n"
" mid = (left + right) // 2\n"
" if arr[mid] == target:\n"
" return mid\n"
" elif arr[mid] < target:\n"
" left = mid\n"
" else:\n"
" right = mid - 1\n"
" return -1\n\n"
"print(binary_search([1, 3, 5, 7, 9], 7))\n"
"```"
),
"expected_winner": "Think High",
"tip": "Think High usually finds the bugs and explains them well.",
},
"System Design": {
"prompt": (
"Design a scalable vector search system for 100 million documents.\n\n"
"Address each of the following:\n"
"1. Indexing strategy and pipeline\n"
"2. ANN algorithm selection (HNSW vs IVF-PQ vs ScaNN — justify your choice)\n"
"3. Sharding and replication strategy\n"
"4. p99 query latency target (< 50 ms) — how do you hit it?\n"
"5. Real-time document update handling\n"
"6. Top 3 failure modes and their mitigations"
),
"expected_winner": "Think High",
"tip": "Think High often gives the best quality-per-dollar design answer.",
},
"Planning": {
"prompt": (
"Create a detailed 6-month roadmap for deploying an enterprise RAG system.\n\n"
"Include:\n"
"- Month-by-month phases with concrete, measurable milestones\n"
"- Top 5 risks and mitigation strategies\n"
"- Team roles and headcount required per phase\n"
"- Evaluation metrics for each phase (how do you know it's working?)\n"
"- Go / no-go production checklist"
),
"expected_winner": "Think High",
"tip": "Think High should produce a more structured, complete roadmap.",
},
"Math (IMO-style)": {
"prompt": (
"Solve this problem completely and verify your answer:\n\n"
"Find all positive integers n such that n² + 1 is divisible by n + 1.\n\n"
"Your answer must include:\n"
"1. A complete proof with clear logical steps\n"
"2. Verification with at least 3 concrete numerical examples\n"
"3. A rigorous argument for why your solution set is complete "
"(i.e., there are no other solutions)"
),
"expected_winner": "Think Max",
"tip": "Think Max earns its cost when thorough verification matters.",
},
}
Pięć zadań czytelnie mapuje się na trzech oczekiwanych zwycięzców:
|
Zadanie |
Oczekiwany zwycięzca |
Uzasadnienie |
|
Trivial / Lookup |
Non-think |
Brak narzutu na rozumowanie; czas myślenia to czysta strata |
|
Coding / Debugging |
Think High |
Jedno metodyczne przejście wyłapuje błędy; Max dodaje marginalną wartość |
|
System Design |
Think High |
Większa głębia poprawia jakość; wyczerpanie nie dodaje sensownej architektury |
|
Planning |
Think High |
Struktura wygrywa z samą mocą obliczeniową; roadmapa to nie dowód |
|
Math (IMO-style) |
Think Max |
Weryfikacja jest kluczowa; zły dowód jest gorszy niż wolny, ale poprawny |
Każde zadanie ma też pole expected_winner. Aplikacja wyświetla je po pojawieniu się wyników, aby użytkownicy mogli sprawdzić, czy przewidywany zwycięzca się potwierdził, czy został pokonany.
Krok 4: Klasa danych
Mając zdefiniowane szablony zadań, potrzebujemy teraz struktury do przechowywania wyników z każdego wywołania API. Zamiast przekazywać surowe obiekty odpowiedzi po całej aplikacji, definiujemy pojedynczy dataclass RunResult, który zbiera wszystkie metryki potrzebne panelowi porównawczemu.
@dataclass
class RunResult:
mode: str
answer: str = ""
thinking: str = ""
latency: float = 0.0
input_tokens: int = 0
output_tokens: int = 0
cost_usd: float = 0.0
error: Optional[str] = None
@property
def tokens_per_second(self) -> Optional[float]:
if self.latency > 0 and self.output_tokens > 0:
return self.output_tokens / self.latency
return None
@property
def thinking_word_count(self) -> int:
return len(self.thinking.split()) if self.thinking else 0
Pole thinking jest oddzielone od answer, ponieważ API DeepSeek V4 zwraca łańcuch rozumowania w dedykowanym polu reasoning_content, na tym samym poziomie co content w strukturze odpowiedzi. Rozdzielenie ich pozwala UI renderować zwijany ślad rozumowania obok czystej finalnej odpowiedzi, co uwidacznia różnice trybów — zwłaszcza w zadaniu matematycznym, gdzie ślad Think Max może liczyć tysiące słów.
Właściwość tokens_per_second mierzy przepustowość generacji niezależnie od długości promptu. Jest to przydatne przy porównywaniu efektywności między trybami, gdy liczby tokenów wyjściowych znacznie się różnią. Zauważ, że Think Max naturalnie generuje więcej tokenów, więc same porównania opóźnień mogą być mylące bez informacji o szybkości generacji w każdym trybie.
Krok 5: Pomocnicze funkcje szacowania kosztów
Zanim napiszemy logikę wywołań API, definiujemy dwa niewielkie helpery, które znajdują się między surową odpowiedzią SDK a RunResult. Łatwo je przeoczyć, ale mają istotne znaczenie dla dokładności kosztów.
def get_cached_prompt_tokens(usage) -> int:
prompt_details = getattr(usage, "prompt_tokens_details", None)
if prompt_details is None:
return 0
cached_tokens = getattr(prompt_details, "cached_tokens", None)
if cached_tokens is not None:
return cached_tokens or 0
if isinstance(prompt_details, dict):
return prompt_details.get("cached_tokens", 0) or 0
return 0
def estimate_cost_usd(
model: str,
prompt_tokens: int,
completion_tokens: int,
cached_prompt_tokens: int,
) -> float:
pricing = PRICING.get(model, PRICING["deepseek-v4-flash"])
cached_tokens = min(cached_prompt_tokens, prompt_tokens)
uncached_tokens = max(prompt_tokens - cached_tokens, 0)
return (
cached_tokens / 1_000_000 * pricing["input_cache_hit"]
+ uncached_tokens / 1_000_000 * pricing["input_cache_miss"]
+ completion_tokens / 1_000_000 * pricing["output"]
)
Funkcja get_cached_prompt_tokens() nie korzysta z bezpośredniego dostępu do pola, ponieważ struktura prompt_tokens_details nie jest gwarantowanie spójna między wersjami SDK. Najpierw sprawdza atrybut typowany, potem przechodzi do dostępu przez dict, a na końcu zwraca zero zamiast podnosić wyjątek. Ma to znaczenie w ThreadPoolExecutor, gdzie cichy błąd w jednym wątku mógłby skutkować zwodniczo niskim oszacowaniem kosztu dla danego trybu bez widocznego błędu.
Helper estimate_cost_usd() dzieli liczbę tokenów w prompecie na część cache-hit i cache-miss, zanim zastosuje stawki. Ponieważ DeepSeek obniżył cenę cache-hit do jednej dziesiątej stawki miss, różnica między „zimnym” a „ciepłym” promptem może być dramatyczna. Przy powtarzanych uruchomieniach Non-think może wydawać się niemal darmowy w porównaniu z Think Max, nie dlatego, że wygenerował mniej tokenów, lecz dlatego, że jego krótszy prompt znacznie częściej trafia do ciepłej pamięci podręcznej.
Krok 6: Równoległe wykonanie wywołań API
To rdzeń architektury areny. Wszystkie trzy tryby startują jednocześnie za pomocą ThreadPoolExecutor, więc całkowity czas ścienny równa się najwolniejszemu pojedynczemu trybowi, a nie sumie trzech.
def call_mode(client: OpenAI, model: str, mode_name: str, user_prompt: str) -> RunResult:
result = RunResult(mode=mode_name)
mode_cfg = MODES[mode_name]
start = time.perf_counter()
try:
request_kwargs = {
"model": model,
"messages": [{"role": "user", "content": user_prompt}],
"max_tokens": 4096,
"extra_body": {"thinking": {"type": mode_cfg["thinking_type"]}},
}
if mode_cfg["reasoning_effort"]:
request_kwargs["reasoning_effort"] = mode_cfg["reasoning_effort"]
response = client.chat.completions.create(**request_kwargs)
result.latency = time.perf_counter() - start
message = response.choices[0].message
result.thinking = (getattr(message, "reasoning_content", None) or "").strip()
result.answer = (message.content or "").strip()
usage = response.usage
result.input_tokens = getattr(usage, "prompt_tokens", 0) or 0
result.output_tokens = getattr(usage, "completion_tokens", 0) or 0
result.cost_usd = estimate_cost_usd(
model=model,
prompt_tokens=result.input_tokens,
completion_tokens=result.output_tokens,
cached_prompt_tokens=get_cached_prompt_tokens(usage),
)
except Exception as exc:
result.latency = time.perf_counter() - start
result.error = str(exc)
return result
def run_parallel(client: OpenAI, model: str, prompt: str) -> Dict[str, RunResult]:
results: Dict[str, RunResult] = {}
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as pool:
futures = {
pool.submit(call_mode, client, model, mode_name, prompt): mode_name
for mode_name in MODES
}
for fut in concurrent.futures.as_completed(futures):
results[futures[fut]] = fut.result()
return results
Warto podkreślić kilka decyzji projektowych:
-
Warunkowe budowanie
request_kwargs: TrybNon-thinkwysyłathinking_type: "disabled"i nie wysyła kluczareasoning_effort.Think HighiThink Maxwysyłająthinking_type: "enabled"i odpowiedni poziom wysiłku. Strażnikif mode_cfg["reasoning_effort"]zapewnia, że parametr jest całkowicie pomijany dlaNon-think, zamiast przesyłaniaNone, co mogłoby wywołać błąd walidacji API. -
Bezpośredni odczyt
reasoning_content: Ślad łańcucha myśli jest odczytywany zmessage.reasoning_contentprzezgetattr()z domyślną pustą wartością. To bardziej odporne niż parsowanie tagów<think>...</think>z treści. Korzysta z oficjalnego pola odpowiedzi, obsługuje przypadek braku śladu (Non-think) i nie psuje się, jeśli model w treści użyje słowa „think” w innym kontekście. -
Obronne
getattr()na polach usage: Pola użycia są pobierane przezgetattr(..., 0) or 0, a nie bezpośrednio. Jeśli obiekt usage nie zawiera pola w starszej wersji SDK lub nietypowej odpowiedzi, dostaniemy zero zamiastAttributeError, który oznaczałby porażkę całego uruchomienia. -
Obsługa błędów: Jeśli wywołanie nie powiedzie się, dataclass
RunResultprzechowuje komunikat błędu zamiast podnosić wyjątek. UI sprawdzaresult.errori renderuje informacyjną kartę bez wykrzaczania całego biegu trzech trybów.
Krok 7: Interfejs Streamlit i CSS
Ten krok obejmuje warstwę prezentacji: układ strony, system projektowania CSS, kolumnę wyników dla każdego trybu i strukturę kart, która porządkuje porównanie bez przytłaczania użytkownika przy pierwszym załadowaniu.
Aplikacja używa st.set_page_config z layout="wide", aby trzykolumnowe porównanie miało dość miejsca w poziomie:
def main():
st.set_page_config(
page_title="DeepSeek V4 Think Mode Arena",
layout="wide",
initial_sidebar_state="expanded",
)
inject_css()
Funkcja inject_css() wstrzykuje pełny system projektowania przez st.markdown. Wykorzystuje ciepłe tło pergaminowe, Space Mono dla nagłówków monospace i żetonów metryk oraz DM Sans dla tekstu głównego.
def render_mode_column(result: RunResult, mode_name: str):
cfg = MODES[mode_name]
badge_cls = f"mode-{cfg['badge']}"
st.markdown(
f'<div class="mode-header {badge_cls}">{mode_name}</div>',
unsafe_allow_html=True,
)
st.markdown(f'<div class="subtle-copy">{cfg["desc"]}</div>', unsafe_allow_html=True)
if result.error:
st.error(f"**API Error:** {result.error}")
return
chips = [
("Latency", f"{result.latency:.1f}s"),
("Output tokens", f"{result.output_tokens:,}"),
("Cost", f"${result.cost_usd:.5f}"),
]
if result.tokens_per_second:
chips.append(("Tok/s", f"{result.tokens_per_second:.0f}"))
chip_html = "".join(
f'<span class="metric-chip">{label} <span>{val}</span></span>'
for label, val in chips
)
st.markdown(chip_html, unsafe_allow_html=True)
if result.thinking:
with st.expander(f" Thinking trace — {result.thinking_word_count:,} words"):
preview = result.thinking[:5000]
if len(result.thinking) > 5000:
preview += "\n\n[… truncated for display …]"
st.text(preview)
elif mode_name != "Non-think":
st.caption("_No thinking trace emitted_")
st.markdown('<div class="answer-label">Final answer</div>', unsafe_allow_html=True)
st.markdown(result.answer if result.answer else "_No answer returned._")
Zwijany podgląd śladu myślenia szczególnie pomaga odczuć różnicę trybów — Think Max może wygenerować tysiące słów autokorekty i weryfikacji przy trudnym zadaniu matematycznym, podczas gdy Non-think może nie mieć śladu wcale.
Główny układ korzysta z kart (tabs) w Streamlit, aby rozdzielić elementy bez upychania wszystkiego w jednym przewijaniu:
overview_tab, answers_tab, ratings_tab = st.tabs(
["Overview", "Full Responses", "Ratings"]
)
Karta „Overview” pokazuje podsumowanie zwycięzcy i tabelę metryk. „Full Responses” wyświetla trójkolumnowe porównanie z śladami myślenia, a „Ratings” zawiera suwaki ocen jakości (1–5) dla każdego trybu.
Krok 8: Metryki i oceny
Ostatni krok buduje dwa komponenty znajdujące się na karcie Overview: płaską tabelę metryk, która zestawia obok siebie wszystkie mierzone wymiary, oraz podsumowanie zwycięzcy, które wskazuje wygranego w czterech kategoriach.
def render_metrics_table(results: Dict[str, RunResult], ratings: Dict[str, int]):
rows = []
for mode_name, res in results.items():
ok = not res.error
rows.append({
"Mode": mode_name,
"Latency (s)": f"{res.latency:.2f}" if ok else "—",
"Input Tokens": f"{res.input_tokens:,}" if ok else "—",
"Output Tokens": f"{res.output_tokens:,}" if ok else "—",
"Tok/s": f"{res.tokens_per_second:.0f}" if (ok and res.tokens_per_second) else "—",
"Est. Cost (USD)": f"${res.cost_usd:.5f}" if ok else "—",
"Thinking Words": f"{res.thinking_word_count:,}" if ok else "—",
"User Rating": f"{ratings.get(mode_name)}/5" if ratings.get(mode_name) else "—",
})
st.table(rows)
def render_winner_summary(results: Dict[str, RunResult], ratings: Dict[str, int], expected: str):
valid = {k: v for k, v in results.items() if not v.error}
fastest = min(valid, key=lambda k: valid[k].latency)
cheapest = min(valid, key=lambda k: valid[k].cost_usd)
most_efficient = max(
valid,
key=lambda k: valid[k].output_tokens / max(valid[k].cost_usd, 1e-9),
)
top_rated = max(ratings, key=ratings.get) if ratings else None
Krótko o tym, co robią te dwie funkcje:
-
Funkcja
render_metrics_table()iteruje po każdymRunResulti buduje wiersz dla każdego trybu. Osiem kolumn obejmuje pełne porównanie: czas (latency, tok/s), skalę (tokeny wejściowe i wyjściowe, słowa w śladzie myślenia), pieniądze (szacowany koszt) i osąd ludzki (ocena użytkownika). -
Funkcja
render_winner_summary()najpierw odfiltrowuje nieudane przebiegi przed wyznaczeniem zwycięzców, więc pojedynczy błąd API nie wypacza wyników. Następnie wskazuje mistrza w czterech niezależnych wymiarach: szybkość czasu ściennego, koszt brutto, efektywność wyjścia i ocena użytkownika.
Te cztery kategorie są celowo rozdzielone zamiast łączone w jeden wynik, bo złożony wskaźnik wymagałby decyzji o ważeniu opóźnienia względem kosztu — to decyzja produktowa, nie ramowa.
Aplikacja pokazuje także przewidywanego zwycięzcę dla zadania i zachęca użytkownika do potwierdzenia zgodności:
st.markdown(
f"> **Expected winner for this task type:** {expected} — "
"does your result match? Rate answers to confirm.",
)
Krok 9: Uruchomienie aplikacji
Cała aplikacja mieści się w pojedynczym pliku app.py i ma dwie zależności, a uruchomienie to dwa polecenia:
# Set your API key
export DEEPSEEK_API_KEY="sk-..."
# Run
streamlit run app.py
Aplikacja otworzy się w przeglądarce pod adresem localhost:8501. Proszę wybrać szablon zadania z listy, opcjonalnie edytować prompt i kliknąć Run Arena. Pasek postępu aktualizuje się wraz z zakończeniem każdego trybu. Wyniki są przechowywane w st.session_state, więc można przełączać karty i oceniać odpowiedzi bez ponownego uruchamiania.

Uwaga dotycząca cen: DeepSeek znacząco skorygował stawki API V4 od czasu premiery. V4-Pro jest obecnie w promocji, a ceny cache-hit obniżono w kwietniu 2026 do jednej dziesiątej stawki miss. Kwoty w aplikacji odzwierciedlają bieżące stawki, ale mogą się zmienić. Zawsze weryfikuj na api-docs.deepseek.com/quick_start/pricing przed korzystaniem z dowolnego modelu.
Podsumowanie
W tym samouczku zbudowaliśmy aplikację Streamlit, która uruchamia ten sam prompt równolegle w trzech trybach rozumowania DeepSeek V4 i porównuje wyniki pod względem opóźnienia, kosztu, użycia tokenów, głębokości śladu myślenia i jakości ocenianej przez użytkownika. Kluczowe wybory architektoniczne to:
-
ThreadPoolExecutordla prawdziwego równoległego wykonania, dzięki czemu całkowity czas ścienny to czas najwolniejszego trybu, a nie suma trzech -
Parametry API
thinkingireasoning_effortzapewniają czystą, explicytną kontrolę trybu, zgodną z API DeepSeek zamiast polegać na sterowaniu promptem systemowym -
Szacowanie kosztów z uwzględnieniem pamięci podręcznej dzieli tokeny wejściowe na części cache-hit i cache-miss, co daje znacznie dokładniejsze kwoty, szczególnie przy powtórnych uruchomieniach, gdy rabaty za ciepłą pamięć podręczną mogą sprawić, że
Non-thinkwyda się niemal darmowy
Aby rozwinąć projekt, warto rozważyć dodanie warstwy LLM-as-judge (z wykorzystaniem oddzielnego modelu, np. Claude lub GPT-4, do automatycznego oceniania odpowiedzi), buforowania odpowiedzi po skrócie promptu, aby unikać ponownego uruchamiania identycznych zapytań, lub dodania osi między modelami, która zestawia Flash Think Max z Pro Think High — to naprawdę ciekawy problem parytetu kosztów, który porusza praca o V4, ale nie odpowiada na niego w pełni.
Najczęściej zadawane pytania o arenę trybów myślenia API DeepSeek V4
Dlaczego korzystamy z OpenAI zamiast SDK DeepSeek?
API DeepSeek jest kompatybilne z OpenAI, co oznacza, że możemy skierować klienta OpenAI na https://api.deepseek.com przy użyciu klucza API DeepSeek. Oznacza to także, że aplikacja zadziała z dowolnym dostawcą kompatybilnym z OpenAI, który hostuje DeepSeek V4 — wystarczy zmienić bazowy URL i nazwę modelu.
Dlaczego dla modelu Non-think pomijamy reasoning_effort zamiast ustawić go na None?
Strażnik if mode_cfg["reasoning_effort"] w call_mode() całkowicie pomija parametr, gdy ma on wartość None, zamiast przesyłać reasoning_effort=None. Niektóre walidatory API odrzucają nieznane parametry z jawnie pustymi wartościami; pomijanie ich jest więc bezpieczniejszym wzorcem przy pracy z nowo wydanymi endpointami.
Dlaczego ślad myślenia czasem nie pojawia się dla Think High?
Ślad jest odczytywany z message.reasoning_content. Jeśli model zwróci puste lub null dla danego przebiegu — co może się zdarzyć przy krótszych, mniej złożonych promptach, gdzie Think High szybko się konwertuje — wówczas result.thinking będzie pustym łańcuchem.
