Een gesprek voortzetten¶
Met de capability threads kun je gespreksgeschiedenis opslaan en bij een volgend verzoek
verdergaan. Per nieuwe beurt hoef je alleen de nieuwe invoer te sturen.
Zonder threads worden gesprekken niet opgeslagen. Een verzoek met previous_response_id,
conversation of thread_id geeft dan 404, en GET /responses/{id} ook. De melding, in het
Engels, zegt waarom:
This key does not hold the `threads` capability, so no response or thread was kept for it to
continue or read back. Send the whole conversation in the request, or use a key that holds
`threads`.
Een sleutel die threads wel heeft en een id noemt dat niet bestaat, krijgt een kale Not Found.
Doorgaan op een antwoord¶
Stuur het id van het vorige antwoord mee met je volgende bericht:
curl -s -X POST "$CORE_URL/responses" \
-H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
-d '{"model": "<een model-id>", "previous_response_id": "resp_0KQ2mR...",
"input": "En in één zin?"}'
Laat bij de eerste opgeslagen aanroep zowel previous_response_id als thread_id weg. Core maakt
een gesprek aan en geeft het id terug in het 201-antwoord. Gebruik het ontvangen antwoord-id om
het gesprek voort te zetten.
Met thread_id kun je ook direct een gesprek kiezen. Stuur maar één van deze velden per verzoek.
Een toolresultaat dat je in een voortgezet gesprek stuurt, moet een aanroep beantwoorden die al in het gesprek staat of die dezelfde invoer herhaalt; een resultaat dat geen van beide beantwoordt, wordt geweigerd (Tools die je applicatie uitvoert).
Een gesprek voortzetten op /messages¶
Het Messages-verzoek van Anthropic heeft geen veld voor een gesprek, en een SDK-client van de
leverancier kan er zonder extra_body geen aan de body toevoegen. POST /messages leest het
gesprek daarom ook uit de verzoekheader X-Core-Thread-Id, dezelfde header die elk opgeslagen
antwoord terugstuurt. Lees hem van het eerste antwoord en zet hem als standaardheader op de client:
first = client.messages.with_raw_response.create(...)
thread = first.headers["X-Core-Thread-Id"]
client = anthropic.Anthropic(default_headers={"X-Core-Thread-Id": thread}, ...)
De header betekent precies wat thread_id in de body betekent. Beide sturen met verschillende
waarden wordt geweigerd met een 400; dezelfde waarde in beide is goed.
Opslaginstellingen¶
| Instelling | Resultaat |
|---|---|
threads ingeschakeld, standaardwaarde voor store |
201 met een Location-header; de beurt wordt opgeslagen. |
store: false |
200; de beurt wordt niet opgeslagen en kan niet via een id worden voortgezet. |
threads uitgeschakeld |
200; de aanroep wordt nooit als gesprek opgeslagen. |
Gebruik store: false voor taken waarvan je de invoer en uitvoer niet hoeft terug te halen, zoals
classificeren of herschrijven. Het verbruik wordt wel geregistreerd.
Opgeslagen geschiedenis lezen¶
| Endpoint | Resultaat |
|---|---|
GET /responses/{response_id} |
Een opgeslagen beurt met de uitvoeritems |
GET /threads |
Gesprekken, gepagineerd met een cursor |
GET /threads/{thread_id} |
De gegevens van een gesprek |
GET /threads/{thread_id}/items |
Berichten op volgorde |
POST /threads/{thread_id}/items |
Items toevoegen zonder een model aan te roepen |
GET /thread-items?q=terugbetaling |
Berichten met bepaalde woorden, over gesprekken heen |
PUT /threads/{thread_id}/items/{item_id}/metadata |
De notitie van een persoon bij een bericht |
DELETE /threads/{thread_id} |
Het gesprek en de berichten verwijderen |
Elke route die een gesprek teruggeeft (POST /threads, GET /threads, GET /threads/{thread_id},
PATCH /threads/{thread_id}) draagt twee velden die een gesprekkenlijst nodig heeft zonder de
items te lezen:
subjectis de persoon voor wie het gesprek is. Een sleutel ziet het op elk gesprek; een subject-token ziet haar eigen id op haar gesprekken ennullop elk gesprek dat niet het hare is, gedeeld of van een ander, precies zoals een zoekresultaat het noemt.message_countsis hoeveelmessage-items het gesprek bevat, per schrijver:{"user": 2, "assistant": 1, "developer": 0}. Een toolaanroep, het resultaat, een redeneerblok en een weigering tellen niet mee. De tellingen worden gemaakt als je het gesprek leest, in één statement voor de hele pagina van een lijst.
Elk item dat een beurt schreef heeft een response_id, het id van het antwoord dat het maakte,
zodat een gespreksgeschiedenis met dat id de trace van elk
antwoord kan openen; een item dat je zelf toevoegde heeft "response_id": null. Elk item draagt
ook subject, de persoon voor wie het verzoek dat het schreef was -- wie het zei, in een gesprek
waarin meerdere mensen schrijven -- en null als dat verzoek
niemand noemde. Iedereen die het gesprek mag lezen ziet het.
Afzonderlijke items kun je na het toevoegen niet bewerken of verwijderen. Een item met een
input_audio-opname wordt geweigerd met 422 op items, op POST /threads en
POST /threads/{thread_id}/items allebei: alleen een beurt transcribeert een opname, dus stuur haar
via POST /responses (Audio transcriberen).
Berichten zoeken¶
Vind de berichten waarin iets staat, in alle gesprekken die je sleutel mag lezen:
curl -s "$CORE_URL/thread-items?q=terugbetaling" -H "Authorization: Bearer $CORE_KEY"
Elke treffer noemt zijn thread_id en item_id, wie het schreef (role), wanneer, en een
snippet van zo'n dertig woorden met onder highlights de start en het end van elke
overeenkomst, geteld in UTF-16-code-units zoals een JavaScript-string telt. Het hele bericht
haal je op met GET /threads/{thread_id}/items/{item_id}. Schrijf q zoals in een
zoekmachine: hele woorden, "een zin tussen aanhalingstekens", or tussen alternatieven en
-woord om er een uit te sluiten. Beperk het met thread_id, thread_metadata=sleutel:waarde,
role (user, assistant of developer), en from en to.
Alleen berichten en weigeringen worden doorzocht, nooit toolaanroepen, toolresultaten of de eigen
mededelingen van Core, en alleen de gesprekken die GET /threads voor hetzelfde verzoek toont: noemt
je verzoek een persoon, dan vindt zij haar eigen gesprekken en de gedeelde. Een bericht dat een
sleutel met pseudonimisering voor het model verborg, vind je op de
echte naam, want het gesprek bewaart wat er geschreven is.
De resultaten komen nieuwste eerst. Een pagina leest een vast deel van je geschiedenis, dus ze kan
kort of leeg terugkomen en toch een next_cursor hebben; searched_to zegt dan tot welke dag
ze terugzocht. Geef de cursor mee om verder terug te zoeken, of beperk from en to.
Notities bij een bericht¶
Bewaar de eigen notitie van je applicatie bij één bericht, zoals een duim omhoog, met
PUT /threads/{thread_id}/items/{item_id}/metadata:
curl -s -X PUT "$CORE_URL/threads/$THREAD/items/$ITEM/metadata" \
-H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
-H 'X-Core-Subject: {"id": "anna"}' \
-d '{"metadata": {"score": "negative", "stars": 2, "liked": false}}'
Een notitie heeft maximaal 16 sleutels, elk een string van hoogstens 512 tekens, een getal of een
boolean; Core leest haar nooit. Elke persoon houdt haar eigen notitie bij een bericht, de persoon
die je verzoek noemt, en een verzoek dat niemand noemt houdt die van de organisatie ernaast, dus
twee mensen die allebei een antwoord beoordelen overschrijven elkaar nooit. Voor een notitie hoeft
het verzoek het gesprek alleen te mogen lezen. PUT vervangt de hele notitie van die persoon,
DELETE haalt haar weg en GET toont de notities bij het bericht; een
subjecttoken ziet en schrijft alleen de eigen notitie.
Vind de berichten die één persoon markeerde met
GET /thread-items?metadata=score:negative&metadata_by=anna, los of naast q. Een waarde komt
overeen met de string en met het getal of de boolean die ze spelt, dus stars:2 vindt 2 en
"2". Elke treffer draagt de notitie die overeenkwam als metadata. Een persoon wissen neemt
elke notitie die zij schreef mee; een persoon die je in een notitie van de organisatie zet
({"by": "anna"}) haal je zelf weg.
Meerdere mensen in één gesprek¶
Een gesprek is van de persoon die het opende, of van niemand. Een groepsgesprek is een gesprek
dat je regels voor meer mensen openen, alleen beschikbaar waar je deployment het aanzet
(threads_group_reach_by_rule, standaard uit); de schrijver van elk item en de volgorde waarin
berichten binnenkomen, hieronder, gelden overal. Om er een te openen schrijf je op je sleutel één
regel die een gesprek opent voor de mensen die een label noemt, en label je het gesprek met hen.
{"id": "thread-participants", "effect": "permit", "kind": "thread",
"actions": ["see", "write"],
"when": [{"atom": "label_is_subject", "label": "participants"}]}
Met "actions": ["see", "write"] lezen en schrijven de genoemde mensen het gesprek, maar
verwijderen ze het niet; daarvoor is een regel nodig die delete noemt. Het beleidsdocument van een
sleutel bestaat precies uit de regels die erin staan, dus zet deze regel naast de regels die er al
in staan: lees het document met GET /key, voeg de regel toe aan rules en stuur het hele document
terug (Je eerste regel schrijven
toont het verzoek). Dan opent het apparaat van Anna een gesprek voor haar en Bob:
curl -s -X POST "$CORE_URL/threads" \
-H "Authorization: Bearer $ANNA_TOKEN" -H "Content-Type: application/json" \
-d '{"labels": {"participants": ["anna", "bob"]}}'
Bobs eigen subject-token toont en leest
dat gesprek nu, voegt er items aan toe, zet het voort met een beurt (thread_id of de
previous_response_id van elk antwoord erin), wijzigt de labels binnen wat zijn regels hem laten
schrijven en hoort de events. Wie aan participants mag worden toegevoegd,
bepaalt writable_labels:
welke waarden elke persoon mag schrijven; zonder die instelling mag iedereen die het gesprek mag
schrijven iedereen noemen. Haal Bob van participants af en dat stopt allemaal bij zijn volgende
verzoek. Een gedeeld gesprek -- een dat je backend voor niemand opende -- is voor een apparaat
alleen-lezen, tenzij de regel de persoon ook daarop noemt: label het met participants en de
genoemde mensen schrijven erin zoals in elk ander groepsgesprek, terwijl de anderen het alleen
lezen.
Wie wat zei. Elk item noemt de schrijver in subject, zodat een chatvenster elk bericht bij de
juiste persoon zet. Bobs beurt in Anna's gesprek is van Bob: het antwoord staat op zijn verbruik en
hij leest het met GET /responses/{id}; het gesprek zelf blijft van Anna.
Twee berichten tegelijk. Elk bericht krijgt zijn plaats op het moment dat het binnenkomt. Core
schrijft de invoer van een beurt in het gesprek vlak voordat het model wordt aangeroepen, en het
antwoord als het model klaar is. Schrijft Bob terwijl Anna's antwoord nog wordt gemaakt, dan staat
er: Anna's bericht, Bobs bericht, Bobs antwoord, Anna's antwoord -- de antwoorden in de volgorde
waarin ze klaar waren. Bobs beurt ziet Anna's bericht, niet haar onaf antwoord; Anna's beurt ziet
Bobs bericht niet. Geen van beide verzoeken wordt geweigerd. Een verzoek met een
idempotency-sleutel schrijft zijn bericht en zijn antwoord samen weg als de beurt klaar is, dus de
anderen zien dat bericht pas dan. Drie situaties laten een bericht achter waarvan de response_id
naar geen antwoord wijst, zodat GET /responses/{id} er 404 op geeft: Core die midden in een
beurt stopt, een onverwachte fout in Core, en een mislukte poging het antwoord weg te schrijven.
Traces. Ieder leest de traces van de beurten van de anderen in het gesprek.
Metadata van een gesprek¶
Met metadata koppel je maximaal 16 sleutel/waarde-paren van strings aan een gesprek:
curl -s -X POST "$CORE_URL/threads" \
-H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
-d '{"metadata": {"dossier": "4711", "kanaal": "support"}}'
Filter gesprekken met GET /threads?metadata=dossier:4711. Herhaal de queryparameter metadata om
op meerdere paren te filteren; elk paar moet overeenkomen. Core slaat metadata op zoals je die
aanlevert en geeft dezelfde waarden terug, en stuurt ze nooit als instructie naar het model.
Metadata bepaalt nooit wie een gesprek bereikt; labels doen dat. Geef labels mee naast
metadata, bijvoorbeeld {"labels": {"department": "payroll"}}, en als je sleutel de persoon noemt
voor wie een verzoek is, kan een regel van de soort thread in het beleid van je sleutel of je
organisatie iedereen van de loonadministratie toelaten. Zonder zo'n regel veranderen labels niets
aan de toegang. Eén release lang leest een regel nog de metadata van een gesprek onder een naam
die zijn labels missen (Labels en metadata).
Zie Labels op alles. De antwoorden in een
gelabeld gesprek, en de geheugenrecords, bestanden en taken die de beurten erin maken, beginnen met
de labels ervan (Labels en metadata).
Noemt een verzoek een persoon, dan kan je policy ook beperken welke waarden zij onder een label mag
schrijven, zodat ze een gesprek niet in een project zet waar ze niet in zit
(Welke labels een persoon mag schrijven).
Een regel van de soort thread kan ook zijn acties
noemen: een permit voor alleen see laat de leden van een project een gesprek lezen zonder er
iets aan toe te voegen, het opnieuw te labelen of het te verwijderen.
Een gesprek verwijderen¶
DELETE /threads/{thread_id} verwijdert de gespreksinhoud. Daarna geeft GET /responses/{id} voor
de bijbehorende antwoorden 404. Core bewaart de antwoord-id's, statussen en verbruiksgegevens. Een
gesprek verwijderen verandert dus niets aan het geregistreerde verbruik.
Gesprekken samenvatten¶
Heeft je sleutel de capability compaction, dan verkort Core wat een lang gesprek aan het model
stuurt. Het telt beurten: een beurt is één verzoek met alles wat Core eraan toevoegde, het
antwoord van het model en de aanroepen en resultaten van de tools die Core draait inbegrepen, en
wordt nooit gesplitst. Een toolresultaat dat je zelf terugstuurt begint de volgende beurt, net als
een gebruikersbericht dat je toevoegt. De laatste keep_recent_turns beurten (vijf, tenzij je
iets anders instelt) gaan altijd volledig mee. Wat daarvoor ligt wordt in segmenten van
segment_turns hele beurten (drie) geknipt, en elk segment wordt vervangen door één samenvatting;
met de standaardwaarden komt de eerste samenvatting bij de achtste beurt. In de modus tiered
worden de oudste segmenten nog eens
gegroepeerd, zodat een stuk gesprek korter leest naarmate het langer geleden is. Een gesprek van
één beurt, hoeveel tool-aanroepen die ook deed, wordt dus nooit samengevat. In de modus window
wordt niets samengevat en gaan alleen de recente beurten mee.
De oorspronkelijke berichten blijven onaangeroerd en beschikbaar via GET /threads/{id}/items;
samenvatten verandert alleen wat het model krijgt. Een samenvatting wordt door een achtergrondtaak
gemaakt vlak na een beurt, dus meestal staat die er al voordat het volgende verzoek binnenkomt; is
er niet op tijd één, dan stuurt Core die items gewoon volledig mee in plaats van ze weg te laten —
een beurt verliest nooit informatie doordat een samenvatting ontbrak.
GET /threads/{thread_id}/summaries toont wat Core voor een gesprek gemaakt heeft: welke posities
elke samenvatting dekt, op welk niveau ze staat, welk model ze schreef en wat er staat.
De grenzen tellen beurten, geen tokens, en garanderen dus niet dat elk gesprek binnen het
contextvenster van het model past. De instellingen keep_recent en segment_items telden items;
een verzoek dat ze nog meestuurt wordt geweigerd met 422. Sleutels en agents die ze hadden
kregen de standaardwaarden in beurten. Een agent kan eigen instellingen voor samenvatten dragen;
die vervangen die van je sleutel voor de beurten die de agent draait.
Een werkgeheugen bijhouden¶
Een gesprek kan naast zijn berichten één klein JSON-object bewaren: zijn werkgeheugen (working state), het kladblok van de taak waar het om gaat -- het doel, de genomen besluiten, de huidige stap. Elke beurt in het gesprek krijgt het te zien direct na zijn instructies en een eventuele samenvatting, vóór de rest van zijn geheugen, en het valt nooit weg bij het samenvatten.
Laat het model het schrijven. Bied de tool aan en het model werkt het werkgeheugen bij met een
JSON merge patch, die Core wegschrijft als de beurt klaar is. De tool vraagt tooling naast
threads, en werkt alleen in een beurt die in een gesprek wordt bewaard:
curl -s -X POST "$CORE_URL/responses" \
-H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
-d '{"model": "<een model-id>", "thread_id": "thr_0KQ2mR...", "input": "Plan de verhuizing.",
"tools": [{"type": "tool", "name": "builtin__update_working_state"}]}'
Een beurt die mislukt of wordt afgebroken schrijft niets, en wat een beurt schrijft vervalt als je
applicatie het werkgeheugen schreef terwijl de beurt liep: het jouwe blijft staan, en het
auditspoor legt thread.working_state_dropped vast. Een agent kan de vorm vastleggen met een JSON Schema,
working_state_schema, op zijn versie; wat niet past wordt geweigerd, met de plek waar het misgaat.
Hoe een aanroep in het gesprek staat. De tool neemt één argument, patch, de JSON merge
patch zelf, en verder niets. De items van een gesprek (GET /threads/{thread_id}/items) tonen de
aanroep als een function_call waarvan arguments dat object als JSON-tekst is, gevolgd door
het resultaat, met de versie waarop het werkgeheugen wordt geschreven en de grootte in bytes:
{
"items": [
{
"id": "itm_0KQ2n8...",
"type": "function_call",
"call_id": "call_7Hf2...",
"name": "builtin__update_working_state",
"arguments": "{\"patch\": {\"goal\": \"Verhuis het kantoor\", \"step\": \"Huur een busje\"}}",
"namespace": null,
"status": "completed",
"response_id": "resp_0KQ2n7..."
},
{
"id": "itm_0KQ2n9...",
"type": "function_call_output",
"call_id": "call_7Hf2...",
"name": "builtin__update_working_state",
"output": "{\"version\": 3, \"size\": 126}",
"error": null,
"artifacts": [],
"citations": [],
"response_id": "resp_0KQ2n7..."
}
],
"next_cursor": null
}
Lees patch uit arguments om te tonen wat het model bij dat antwoord veranderde. Een patch is
een wijziging, niet het hele werkgeheugen: hij wordt samengevoegd met wat er al stond. Een
aanroep met error ingevuld en output leeg werd geweigerd, en zijn patch is niet toegepast. Een
patch blijft ook in het gesprek staan als de beurt mislukte of het schrijven verviel; lees het
werkgeheugen zelf met GET /threads/{thread_id}/state als je wilt weten wat er nu bewaard is.
Zelf lezen en schrijven.
| Endpoint | Resultaat |
|---|---|
GET /threads/{thread_id}/state |
Het werkgeheugen, zijn version ("0" vóór de eerste schrijfactie) en waar het vandaan kwam |
PUT /threads/{thread_id}/state |
Vervang het werkgeheugen door het JSON-object dat je stuurt |
PATCH /threads/{thread_id}/state |
Pas een JSON merge patch toe: null haalt een veld weg |
Stuur de gelezen version mee als If-Match; heeft iemand intussen geschreven, dan krijg je 409
met reason_code: "stale_version". Een werkgeheugen is standaard hooguit 16 KiB; een groter
werkgeheugen wordt met 422 geweigerd en nooit ingekort. Een werkgeheugen dat geschreven is terwijl
woorden van een verzegelde kopie in het gesprek stonden (Publiceren en installeren via de
API), lees je als "[sealed]"; dat kun je niet patchen,
alleen in zijn geheel vervangen met PUT.
Meenemen naar een nieuw gesprek. Noem een eerder gesprek als working_state_from als je een
nieuw begint, op POST /threads of op de POST /responses die het gesprek aanmaakt. Het nieuwe
gesprek begint met een kopie; het eerdere houdt zijn eigen. Het verzoek moet de persoon noemen voor
wie het is -- met X-Core-Subject, een subject-token of user (Een subject meesturen op een
beurt) -- want een kladblok
gaat alleen mee tussen de eigen gesprekken van één persoon (Geheugen):
curl -s -X POST "$CORE_URL/responses" \
-H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
-H 'X-Core-Subject: {"id": "anna"}' \
-d '{"model": "<een model-id>", "input": "Waar waren we?", "working_state_from": "thr_0KQ2mR..."}'
Alleen een gesprek van dezelfde persoon, dat je sleutel mag lezen, kan genoemd worden; elk ander
geeft 404. Bij een verzoek dat een gesprek voortzet, wordt het geweigerd. POST /messages kan
nog geen kladblok meenemen.