"mapping": {
"<node-id>": {
"id": "<node-id>",
"message": { … } // nebo null pro kořen
"parent": "<parent-node-id>" // nebo null
"children": ["<child-id-1>", …]
},
…
}
Zcela klíčové pro orientaci ve stromu mapping
jsou tyto identifikátory:
-
id
(uzlu / node-id)
-
Unikátní řetězec (většinou UUID), který označuje právě tento uzel ve slovníku mapping
.
-
Používá se jako klíč i v node["id"]
.
-
parent
(ve slovníku mapping
)
-
Hodnota je id
nadřazeného uzlu.
-
Každý uzel (kromě „root“) má jednoho parent
, tedy uzel, ze kterého tato zpráva/podstrom logicky vychází.
-
Uzel s parent: null
(většinou "client-created-root"
) je počáteční „kořen“ sezení.
-
children
-
Pole ["id1", "id2", …]
– seznam id
všech potomků (uzly, které navazují na daný uzel).
-
V jednoduchém lineárním chatu bude každé pole children
obsahovat právě jeden další uzel, u vícevětvených scénářů jich může být víc.
-
message.id
-
Stejné jako uzlové id
, ale uvnitř objektu message
.
-
Slouží k unitárnímu mapování mezi uzlem a jeho message
.
-
metadata.parent_id
(uvnitř message.metadata
)
-
Duplicitně ukládá parent
i přímo v metadatech té zprávy.
-
Může se hodit pro rychlé filtrování přímo na úrovni message
, aniž bys musel prolézat mapping
.
-
metadata.request_id
-
Identifikátor konkrétního API požadavku / uživatelského promptu, ze kterého tah vznikl.
-
Pokud jde o uživatelský vstup, request_id
jej spojuje s interním zpracováním.
-
async_task_id
/ async_task_title
-
mapping
vs. jsonData
-
jsonData
je pole sezení (chat sessions). Každý prvek pole je jedno celé sezení/chat (má svůj title
, create_time
apod.).
-
Uvnitř každé session se zcela odděleně nachází mapping
, tedy strukturální strom všech uzlů ve vlastním rozhovoru.
Jak je to navázané:
-
Top-level:
[
{ // session #1
"title": "...",
"mapping": { ... }
},
{ // session #2
...
}
]
-
V každé session:
-
V objektu message
najdeš:
-
message.id
(jaký uzel to je),
-
message.metadata.parent_id
(pro rychlé rozpoznání rodiče),
-
message.metadata.request_id
(který uživatelský dotaz to vyvolal),
-
někdy i async_task_id
a další příznaky režie.
Zjednodušené schéma
jsonData: [
session₁ {
mapping: {
node₁ {
id: node₁
parent: null
children: [node₂]
message: null ← virtuální root
}
node₂ {
id: node₂
parent: node₁
children: [node₃]
message: {
id: node₂
author: { role: user }
metadata: {
parent_id: node₁
request_id: abc-123
is_async_task_result_message: false
}
}
}
node₃ {
id: node₃
parent: node₂
children: []
message: {
…
metadata: {
parent_id: node₂
request_id: abc-123
async_task_id: long-job-456
async_task_title: "…"
}
}
}
}
},
session₂ { … }
]
-
parent
/parent_id
udávají, „ze kterého kroku/vstupu“ daná zpráva vzešla.
-
children
: na co navazuje dál.
-
request_id
: váže to k tomu, co jsi skutečně poslal „ven“ jako svůj prompt.
-
async_task_*
: značí, že odpověď vznikla vícefázově (nejen bezprostředně po promptu).
* * *
* * *
Ta pole start_ix
a end_ix
udávají přesné pozice (offsety) v textovém řetězci, kde v dané zprávě citace začíná a končí. V praxi to znamená:
-
Odkazují na znaky v té samé proměnné msg["content"]["parts"][0]
nebo v msg["content"]["parts"]
po sloučení.
-
Jsou to 0-based indexy znaků (v JavaScriptu se to obvykle měří jako UTF-16 code units, v Pythonu jako Unicode code points).
Co to znamená pro převod \uXXXX
→ skutečné diakritické znaky
-
Pokud JSON načteš a necháš Python či JavaScript automaticky dekódovat ty unicode escape sekvence (\u00fd
→ ý
apod.), pak parts[0]
uloží v paměti skutečné znaky.
-
Potom start_ix
a end_ix
odpovídají právě těmto dekódovaným znakům.
text = msg["content"]["parts"][0]
# text už obsahuje např. 'TJV má velmi nízkou...'
citace = text[start_ix:end_ix]
-
Nezmění se ti tedy offsety, pokud je dekóduješ před použitím těch indexů.
Kdy nastává problém?
-
Pokud bys nejprve počítal znaky nad původním JSON řetězcem se \uXXXX
(tj. escape syntaxí), a pak nahradil \u00fd
znakem ý
, pak by se ti indexy posouvaly (protože \u00fd
je 6 znaků, ale ý
je jen 1 znak).
-
Správný postup je:
-
Načíst JSON a nechat ho plně dekódovat (json.loads
v Pythonu, nebo JSON.parse
v JS).
-
Teprve na dekódovaném text = parts[0]
pak aplikovat start_ix:end_ix
.
To zajistí, že offsety sedí na finální podobu řetězce s diakritikou a emoji. Pokud bys ale nejprve dekódovaný text znovu kódoval zpátky do \uXXXX
, nebo manipuloval s řetězcem jiným způsobem (vkládal/odstraňoval znaky), tak bys ty indexy samozřejmě rozházel.
Závěr:
-
start_ix
/end_ix
jsou offsety v již dekódovaném textu zprávy.
-
Dekódování \uXXXX
→UTF-8 (= skutečné diakritické znaky) proto nezmění jejich význam, pokud proběhne před jejich aplikací.
-
Pokud ale unicode escapes převádíš početně nevhodným krokem (např. ve zdrojovém JSONu), pak ano, indexy by neseděly. Proto:
-
zavolej data = json.loads(raw)
-
text = data[…]["content"]["parts"][0]
-
substring = text[start_ix:end_ix]
a budeš mít přesně tu část zprávy, kterou citace popisuje.
* * *
V metadata zpráv se kromě textových částí (content.parts
) nachází ještě pole citations
(případně citations
+ další typy poznámek) s těmito nejčastějšími formáty:
1. "type": "webpage"
Objekt popisuje základní citaci zdroje – má:
-
start_ix
/ end_ix
Pozice v dekódovaném textu (parts[0]
) od – do (UTF-8 znaky), kde se citace nachází.
-
metadata
podklíč s těmito poli:
-
type: "webpage"
-
title
– titul stránky
-
url
– odkaz
-
text
– vypíchnutý úsek původního textu (short snippet)
-
cited_message_id
/ cited_message_idx
– na kterou předchozí zprávu se citace odkazuje
-
evidence_text
– další poznámka (často prázdné)
Struktura:
{
"start_ix": 423,
"end_ix": 435,
"citation_format_type": "tether_v4",
"metadata": {
"type": "webpage",
"title": "Mapování psychologických tendencí…",
"url": "https://…",
"text": "Uživatel TJV ve svých příspěvcích…",
"cited_message_id": "cc5d078c-…",
"evidence_text": ""
}
}
2. "type": "webpage_extended"
Rozšířená citace, přidává další kontext a ověřené údaje:
-
matched_text
Přesná sekvence z originálního textu, kterou citace popisuje (např. “[28]L49-L52”
).
-
snippet
Kratší úryvek z cílové webové stránky (nemusí být totožné s metadata.text
).
-
attribution
Zdroj domény (např. zpovednica.blogspot.com
).
-
icon_type
Ikonka zdroje (většinou null
).
-
pub_date
Datum publikace zdroje (často null
).
-
alt
Alternativní popisek (většinou null
).
Struktura:
{
"matched_text": "“[28]L49-L52”",
"start_idx": 3505,
"end_idx": 3517,
"type": "webpage_extended",
"title": "Mapování psychologických tendencí…",
"url": "https://…",
"pub_date": null,
"snippet": "sarkasticky, její tón je převážně kritický…",
"attribution": "zpovednica.blogspot.com",
"icon_type": null
}
3. is_async_task_result_message
Když vidíš v message.metadata
klíč
"is_async_task_result_message": true,
"async_task_id": "...",
"async_task_title": "Psychologická a behaviorální analýza…"
znamená to, že tato zpráva je výsledkem asynchronní úlohy (např. delšího API volání / batchového procesu).
Jak to celé použít
-
Rozdělení obsahu
-
Správné offsety
-
Asynchronní zprávy
Tím máš kompletní přehled, jak v datech najít samotný text, veškeré citace a jak rozpoznat, že zpráva vznikla v rámci asynchronní operace.
* * *
V tomhle úseku jsi narazil na několik nových polí a formátů, které obohacují chování a metadata jednotlivých uzlů – pojďme si je rychle projít:
… "content": {
"content_type": "text",
"parts": ["… ;\n</html>"]
},
"status": "finished_successfully",
"end_turn": true,
"weight": 1.0,
"metadata": {
"citations": [],
"content_references": [], ← nově vidíš tohle pole
"message_type": null,
"model_slug": "o4-mini",
"default_model_slug": "auto",
"parent_id": "7d32f62e-…",
"request_id": "94829d82bd8f8033-MXP",
"timestamp_": "absolute",
"is_async_task_result_message": true, ← a i tohle
"b1de6e2_rm": true,
"async_task_id": "deepresch_683a…",
"async_task_title": "Psychologická a behaviorální analýza…"
},
"recipient": "all",
"channel": "final"
…
1. content_references
– Prázdné pole tady, ale obecně by mohlo obsahovat odkazy na externí entity, obrázky, nebo další assety, které se v textu objevují. Pokud bys měl např. vestavěné grafy, videa apod., sem by se zapsal jejich popis/odkaz.
2. status
, end_turn
, weight
– status
: stav vykonání (např. "finished_successfully"
znamená, že celý backend proces doběhl ok).
– end_turn
: zda tah končí (pokud true
, uživatel má možnost vložit nový vstup).
– weight
: interní váha nebo priorita v stromu — obvykle všechny outputy mají 1.0
.
3. model_slug
/ default_model_slug
– Označují, který model odpověď generoval ("o4-mini"
). Pokud se měnil model, default_model_slug
to může zaznamenat.
4. timestamp_
– Zde "absolute"
znamená, že create_time
v tomto message je absolutní Unix timestamp (nikoli relativní).
5. Asynchronní tasky:
-
is_async_task_result_message: true
říká, že tah vznikl jako výsledek dlouhé asynchronní operace (např. batchové zpracování, složitější analýza).
-
async_task_id
/ async_task_title
ukládají interní ID a popisek té úlohy („Psychologická a behaviorální analýza TJV…“).
-
A b1de6e2_rm
je jen interní flag, že se jedná o rebase_developer_message
nebo podobné značení uvnitř systému.
6. channel
– Hodnota "final"
značí, že tohle je konečné zpracování odpovědi asistenta.
– Kromě něj můžou být i jiné kanály (např. "analysis"
, "thoughts"
, apod.), ale konečný výstup vždy jde na "final"
.
Celkově to znamená, že:
-
Nejsou to už jen tvoje požadavky a odpovědi, ale v datech vidíš úplnou historii celého procesu generování:
-
request (uživatel)
-
system mezikroky (thoughts
, reasoning_recap
)
-
asynchronní výpočty (dlouhé úlohy)
-
finální output (channel: "final"
)
-
Metadata ti tím dávají detailní auditní stopu:
-
Který model to vypočítal,
-
zda to byla asynchronní taska,
-
jestli to ukončilo turn,
-
jaké externí assety by se mohly objevit (content_references
).
To ti umožňuje přesně rekonstruovat, jak a odkud každá část odpovědi vznikla.