Kimi K3.1 Pro API: JSON z tekstu i walidacja
Sprawdzam w praktyce, jak wyciągać czysty JSON z tekstu za pomocą Kimi K3.1 Pro API. Przedstawiam gotowy kod w Pythonie ze zmienną środowiskową, bezpieczną walidację schematu bez wykonywania treści oraz obsługę błędów przy ucięciu odpowiedzi na skutek drenażu budżetu wnioskowania.

👁 121 przeczytań
- Kimi K3.1 Pro API poprawnie wyodrębnił obiekty JSON z polskiego tekstu w 3,553 sekundy przy koszcie 0,002004 USD, zwracając dane bez znaczników markdown.
- Przy zadaniach z brakującymi danymi model może zużyć nawet 1797 z 1800 dostępnych tokenów na ukryty łańcuch myśli, nie zostawiając miejsca na odpowiedź.
- Oficjalna dokumentacja deklaruje okno kontekstowe 1 048 576 tokenów przy cenie 2 USD za 1M tokenów wejściowych i 10 USD za 1M tokenów wyjściowych dla wariantu Pro.
Integracja modelu Kimi K3.1 Pro w potokach ekstrakcji danych wymaga ścisłej kontroli nad formatem wyjściowym i budżetem tokenów wnioskowania (reasoning). W moim teście API model bezbłędnie wyodrębnił obiekty JSON z polskiego tekstu w 3,553 sekundy przy koszcie 0,002004 USD, jednak przy zadaniach sprawdzających brak danych potrafił zużyć cały limit max_tokens na ukryty łańcuch myśli i zwrócić null.
- Ekstrakcja strukturalna: Zadanie wyciągnięcia listy obiektów z tekstu oraz obliczenia pamięci w formacie JSON zakończyły się statusem
stopi czystym wynikiem bez znaczników markdown. - Narzut wnioskowania: Nawet przy parametrze
reasoning effort: lowmodel generuje od 101 do 767 tokenów myśli w udanych zapytaniach, a w skrajnych przypadkach pochłania 1797 z 1800 dostępnych tokenów, nie zostawiając miejsca na odpowiedź. - Bezpieczeństwo kodu: Walidacja w Pythonie musi sprawdzać
finish_reasonprzed parsowaniemjson.loads()i nigdy nie może używaćeval()na surowym tekście z API. - Kontekst i stawki: Oficjalna dokumentacja (platform.kimi.ai) deklaruje okno 1 048 576 tokenów przy cenie 2 USD za 1M tokenów wejściowych i 10 USD za 1M wyjściowych dla wariantu Pro.
Parametry techniczne i specyfika integracji Kimi K3.1 Pro API
W moich testach wykorzystałem bramkę OpenRouter (identyfikator modelu moonshotai/kimi-k3.1-pro), ustawiając stały protokół wywołań: temperatura 0.2 dla zwiększenia determinizmu, niskie zaangażowanie wnioskowania (reasoning effort: low), max_tokens=1800 oraz tryb bez strumieniowania (non-stream). Mierzony przeze mnie czas całkowity (wall time) pojedynczego wywołania obejmuje narzut sieciowy oraz czas potrzebny na wygenerowanie ukrytych tokenów myśli, więc z pojedynczego przebiegu nie należy wyliczać prędkości generowania tokenów na sekundę ani czasu do pierwszego tokenu (TTFT). Pełny opis podstawowej rodziny modeli zamieściłem w hubie Kimi.
Minimalne wywołanie API w Pythonie bez zaszywania klucza
Klucz pobieram ze zmiennej środowiskowej OPENROUTER_API_KEY. Przykład korzysta z synchronicznego POST i standardowej biblioteki Pythona; nie umieszczam sekretu w kodzie.
import os
import json
import urllib.request
API_KEY = os.environ.get("OPENROUTER_API_KEY")
if not API_KEY:
raise RuntimeError("Brak zmiennej OPENROUTER_API_KEY")
URL = "https://openrouter.ai/api/v1/chat/completions"
payload = {
"model": "moonshotai/kimi-k3.1-pro",
"temperature": 0.2,
"max_tokens": 1800,
"reasoning": {"effort": "low"},
"messages": [
{
"role": "user",
"content": (
"Zwróć wyłącznie poprawny JSON bez markdown. "
"Z tekstu wyciągnij listę obiektów z polami "
"nazwa, sztuki, cena_netto. Tekst: Anna zamówiła "
"3 notesy po 12,50 zł netto i 2 długopisy po "
"4 zł netto. Nie dodawaj innych pól. Nazwy w "
"liczbie pojedynczej: notes, długopis."
)
}
]
}
req = urllib.request.Request(
URL,
data=json.dumps(payload).encode("utf-8"),
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
},
method="POST"
)
with urllib.request.urlopen(req, timeout=60) as resp:
raw_body = resp.read().decode("utf-8")
data = json.loads(raw_body)
choice = data["choices"][0]
print("Finish reason:", choice.get("finish_reason"))
print("Content:", choice["message"].get("content"))Ustawienie "reasoning": {"effort": "low"} jest krytyczne przy zadaniach strukturalnych. Informuje ono API, aby model ograniczył długość wewnętrznych rozważań przed zwróceniem odpowiedzi. Jak pokażą moje dalsze testy, parametr ten pomaga w typowej ekstrakcji JSON, ale nie zawsze chroni przed wpadnięciem modelu w pętlę analityczną przy podchwytliwych promptach.
Własny test ekstrakcji JSON i oczekiwany schemat obiektu
Aby sprawdzić, jak Kimi K3.1 Pro radzi sobie z polską fleksją, zamianą przecinków dziesiętnych na kropki w standardzie JSON oraz rygorem pomijania bloków markdown (```json), przeprowadziłem dwa testy zwracające czysty JSON: ekstrakcję pozycji zamówienia z tekstu (zadanie json) oraz wyliczenie rozmiaru wag dla hipotetycznego modelu 2,8 biliona parametrów (zadanie pamiec).
W zadaniu json oczekiwałem tablicy zawierającej dokładnie dwa obiekty o ściśle określonych kluczach i typach danych: pole nazwa jako łańcuch znaków w mianowniku liczby pojedynczej ("notes" oraz "długopis"), pole sztuki jako liczba całkowita (3 oraz 2) i pole cena_netto jako liczba (12.5 oraz 4). Model Kimi K3.1 Pro zwrócił dokładnie taki ładunek:
[
{"nazwa": "notes", "sztuki": 3, "cena_netto": 12.5},
{"nazwa": "długopis", "sztuki": 2, "cena_netto": 4}
]Żadnych zbędnych komentarzy, żadnych znaczników markdown ani dodatkowych kluczy. W drugim zadaniu (pamiec) poprosiłem o wyliczenie rozmiaru samych wag dla hipotetycznego modelu 2.8e12 parametrów (nawiązującego do architektury poprzednika K3 z repozytorium huggingface.co/moonshotai, gdyż wagi K3.1 mają zostać opublikowane dopiero do 2026-11-04) i zwrócenie wyniku jako obiektu JSON z kluczami FP8, Q4 i Q2. Ponownie otrzymałem nieskazitelny ciąg znaków: {"FP8":2.8,"Q4":1.4,"Q2":0.7}.
Poniższa tabela zestawia surowe wyniki z mojego protokołu testowego dla zadań JSON na tle konkurencyjnych modeli zaplanowany wspólny test po polsku oraz zaplanowany wspólny test po polsku (zobacz zaplanowane porównania bezpośrednie w linkach).
| Model (zadanie) | Czas (s) | Tokeny (wej / wyj / myśl) | Koszt (USD) |
|---|---|---|---|
| Kimi K3.1 Pro (json) | 3,553 | 197 / 161 / 101 | 0,002004 |
| Claude Opus 5.5 (json) | 2,906 | 152 / 61 / 0 | 0,001828 |
| GPT-6 Astra (json) | 3,488 | 98 / 46 / 0 | 0,003280 |
| Kimi K3.1 Pro (pamiec) | 3,291 | 185 / 159 / 122 | 0,001960 |
| Claude Opus 5.5 (pamiec) | 5,535 | 137 / 102 / 69 | 0,002588 |
| GPT-6 Astra (pamiec) | 3,614 | 93 / 26 / 0 | 0,002230 |
Walidacja struktury JSON w Pythonie bez wykonywania danych wyjściowych
Nawet jeśli model w testach zwraca poprawny format, w kodzie produkcyjnym odpowiedź z LLM trzeba traktowaćć jako niezaufane dane wejściowe (untrusted input). Pod żadnym pozorem nie wolno parsować odpowiedzi za pomocą funkcji eval() ani wykonywać niezwalidowanego kodu. Własny parser powinien składać się z trzech warstw obronnych: weryfikacji metadanych odpowiedzi API, bezpiecznej deserializacji przez json.loads() oraz ścisłego sprawdzenia typów i kluczy schematu.
Poniżej prezentuję kompletną funkcję walidującą napisaną w czystym Pythonie, która sprawdza dokładny schemat naszego zamówienia i odrzuca jakiekolwiek odchylenia (np. brakujące klucze, nadmiarowe pola wyhalucynowane przez model, ujemne liczby czy błędne typy takie jak wartości logiczne bool, które w Pythonie dziedziczą po typie int).
import json
import math
EXPECTED_KEYS = {"nazwa", "sztuki", "cena_netto"}
ALLOWED_NAMES = {"notes", "długopis"}
def validate_order_json(raw_text: str) -> list:
if not isinstance(raw_text, str) or not raw_text.strip():
raise ValueError("Pusta odpowiedź z modelu")
try:
parsed = json.loads(raw_text)
except json.JSONDecodeError as err:
raise ValueError(f"Błąd składni JSON: {err}") from err
if not isinstance(parsed, list):
raise ValueError("Oczekiwano listy obiektów JSON")
if len(parsed) != 2:
raise ValueError("Oczekiwano dwóch pozycji")
validated = []
for idx, item in enumerate(parsed):
if not isinstance(item, dict):
raise ValueError(f"Element {idx} nie jest obiektem")
keys = set(item.keys())
if keys != EXPECTED_KEYS:
raise ValueError(
f"Niezgodne klucze w elemencie {idx}: {keys}"
)
nazwa = item["nazwa"]
sztuki = item["sztuki"]
cena = item["cena_netto"]
if not isinstance(nazwa, str) or nazwa not in ALLOWED_NAMES:
raise ValueError(f"Niepoprawna nazwa: {nazwa!r}")
if isinstance(sztuki, bool) or not isinstance(sztuki, int):
raise ValueError(f"Pole sztuki musi być int: {sztuki!r}")
if isinstance(cena, bool) or not isinstance(cena, (int, float)):
raise ValueError(f"Cena musi być liczbą: {cena!r}")
if not math.isfinite(cena):
raise ValueError("Cena musi być skończona")
if sztuki <= 0 or cena < 0:
raise ValueError("Wartości muszą być dodatnie")
validated.append({
"nazwa": nazwa,
"sztuki": sztuki,
"cena_netto": float(cena)
})
if {x["nazwa"] for x in validated} != ALLOWED_NAMES:
raise ValueError("Po jednej pozycji każdego produktu")
return validatedDlaczego w powyższym kodzie jawnie sprawdzam isinstance(sztuki, bool)? W języku Python typ bool jest podklasą int (isinstance(True, int) zwraca True). Gdyby model zwrócił uszkodzony obiekt {"nazwa": "notes", "sztuki": true, "cena_netto": 12.5}, naiwna walidacja oparta wyłącznie na isinstance(x, int) przepuściłaby wartość logiczną jako liczbę sztuk równą 1. Tego typu detale decydują o stabilności integracji API.
Obsługa pustych odpowiedzi, ucięcia tokenów (finish_reason: length) i braku danych
Nie przepłacaj za te subskrypcje
Prowadzę sklep z rocznymi dostępami do narzędzi AI - te same konta, o których piszę wyżej, tylko taniej niż w cenniku producenta.
Najpoważniejsza pułapka, na jaką natrafiłem podczas testowania Kimi K3.1 Pro API, ujawniła się w zadaniu weryfikującym odporność na halucynacje przy brakujących informacjach (zadanie brak). Prompt brzmiał: „Na podstawie wyłącznie notatki odpowiedz na pytanie. Notatka: Produkt Alfa kosztuje 90 zł. Produkt Beta kosztuje 120 zł. Pytanie: Jaką marżę procentową ma Alfa? Jeżeli danych brakuje, napisz dokładnie BRAK DANYCH.”
Zarówno Claude Opus 5.5 (w 2,757 s, 31 tokenów myśli), jak i GPT-6 Astra (w 2,293 s, 0 tokenów myśli) bez problemu zwróciły ciąg BRAK DANYCH ze statusem finish_reason: "stop". Co zrobił Kimi K3.1 Pro? Przy standardowym limicie protokołu max_tokens=1800 zapytanie trwało aż 21,567 s, zwróciło kod HTTP 200, ale w polu answer (message.content) znalazła się wartość null! Powodem był status finish_reason: "length". Analiza obiektu usage pokazała, że model wygenerował 1800 tokenów wyjściowych, z czego aż 1797 stanowiły ukryte reasoning_tokens.
W korekcie polszczyzny wynik również był pusty: 37,078 s, 1797 tokenów rozumowania. Osobne powtórki z limitem 6000 także zakończyły się flagą length. Nie łączę ich z porównaniem przy 1800. W osobnym, łatwiejszym zestawie 10 zadań z limitem 16000 Pro ukończył 10/10. Dobór budżetu i zadania zmienia wynik.
Należy jasno rozróżnić przyczynę tego stanu: pusta odpowiedź ze statusem finish_reason: "length" to efekt ucięcia generowania po wyczerpaniu budżetu tokenów na łańcuch myśli, a nie fałszywa odpowiedź merytoryczna czy błąd znajomości języka. Dla programisty oznacza to konieczność napisania twardej obsługi odpowiedzi przed przekazaniem jej do parsera JSON:
def extract_content_safely(api_response: dict) -> str:
choices = api_response.get("choices", [])
if not choices:
raise RuntimeError("Brak tablicy choices w odpowiedzi API")
choice = choices[0]
finish_reason = choice.get("finish_reason")
message = choice.get("message") or {}
content = message.get("content")
usage = api_response.get("usage") or {}
comp_details = usage.get("completion_tokens_details") or {}
reasoning_toks = comp_details.get("reasoning_tokens", 0)
if finish_reason == "length":
raise RuntimeError(
f"Przekroczono limit max_tokens. "
f"Tokeny wnioskowania: {reasoning_toks}. "
f"Odpowiedź została ucięta przed wygenerowaniem JSON."
)
if finish_reason != "stop":
raise RuntimeError(
f"Nieoczekiwany status zakończenia: {finish_reason}"
)
if content is None or not str(content).strip():
raise ValueError(
"Model zwrócił pusty content mimo statusu stop"
)
return str(content).strip()Wprowadzenie powyższej funkcji chroni aplikację przed awarią typu TypeError: the JSON object must be str, bytes or bytearray, not NoneType i pozwala od razu zalogować anomalie kosztowe wynikające z zapętlenia procesu wnioskowania.
Budżet wnioskowania (reasoning_tokens) a koszty wywołań w praktyce
Planując wykorzystanie Kimi K3.1 Pro API do masowej obróbki dokumentów, musisz monitorować stosunek tokenów myśli do właściwych tokenów odpowiedzi. Przyjrzyjmy się wszystkim moim zadaniom z głównego protokołu testowego (max_tokens=1800) dla tego modelu:
| Zadanie | Status | Tokeny wyjściowe (w tym myśl) | Koszt (USD) |
|---|---|---|---|
| pamiec (JSON) | stop | 159 (122 myśli) | 0,001960 |
| json (JSON) | stop | 161 (101 myśli) | 0,002004 |
| rachunek (tekst) | stop | 617 (479 myśli) | 0,006548 |
| kod (Python) | stop | 826 (767 myśli) | 0,008632 |
| polski (korekta) | length (null) | 1800 (1797 myśli) | 0,018310 |
| brak (notatka) | length (null) | 1800 (1797 myśli) | 0,018348 |
Najczęstsze pytania (FAQ)
Czy Kimi K3.1 Pro API zwraca czysty JSON bez znaczników markdown?
Tak. W moich testach (zadania json oraz pamiec) po jawnym zażądaniu w prompcie zwrotu wyłącznie poprawnego formatu JSON bez markdown model zwrócił czyste ciągi znaków zaczynające się od nawiasu [ lub {, gotowe do bezpośredniego przekazania do funkcji json.loads().
Dlaczego pole content w odpowiedzi Kimi K3.1 Pro bywa równe null przy kodzie HTTP 200?
Dzieje się tak, gdy model zużyje całą pulę max_tokens na ukryty proces wnioskowania (reasoning_tokens). W moich testach brak i polski model wygenerował 1797 tokenów myśli przy limicie 1800, co zakończyło generowanie statusem finish_reason: "length" przed wypisaniem właściwej odpowiedzi tekstowej.
Ile kosztuje ekstrakcja pojedynczego obiektu JSON w Kimi K3.1 Pro?
W moim teście wyciągnięcia dwuelementowej listy obiektów z krótkiego tekstu (197 tokenów wejściowych i 161 wyjściowych, w tym 101 tokenów myśli) pojedyncze wywołanie kosztowało 0,002004 USD. Oficjalne stawki bazowe to 2 USD za milion tokenów wejściowych i 10 USD za milion wyjściowych (przed podatkami).
Jak bezpiecznie walidować JSON zwrócony przez model w Pythonie?
Należy najpierw upewnić się, że finish_reason == "stop" i treść nie jest pusta, następnie sparsować tekst wyłącznie za pomocą json.loads() (nigdy eval()), a na końcu zweryfikować dokładny zestaw kluczy oraz typy wartości, pamiętając o wykluczeniu typu bool przy sprawdzaniu liczb całkowitych (int).
Źródła
- Oficjalny blog Kimi - zapowiedź Kimi K3.1 Pro i dostępności API (data odczytu: 2026-10-23)
- Platforma API Moonshot AI / Kimi - cennik główny (data odczytu: 2026-10-23)
- Dokumentacja techniczna Kimi API - limity okna kontekstowego i zasady rozliczeń (data odczytu: 2026-10-23)
- Repozytorium Moonshot AI na Hugging Face - odniesienie do architektury K3 (data odczytu: 2026-10-23)
- Cennik instancji GPU Lambda (data odczytu: 2026-10-23)
Benchmarki wg Moonshot
Wg Moonshot, 23.10.2026. Wyniki producenta, nie mój pomiar. Różne testy nie są wymienne.
| Benchmark | Kimi K3.1 Pro | Claude Opus 5.5 | GPT-6 Astra |
|---|---|---|---|
| Terminal-Bench 4.0 | 65.7 | 66.4 | 58.2 |
| SWE Marathon v1.1 | 51.7 | 46.3 | 42.5 |
| HLE-Full | 51.1 | 59.8 | 53.6 |
Niezależny indeks Artificial Analysis: 55,1 (24.10.2026).
Cały tydzień w AI, w jednym mailu
Wybrane premiery, narzędzia i analizy. Raz w tygodniu, prosto do skrzynki.
Zapisz się za darmo →Link do Kimi jest partnerski: jeśli kupisz przez niego, serwis dostaje prowizję, a Ty płacisz tyle samo. Nie wpływa to na treść, także tam, gdzie piszę krytycznie.
