Ga naar inhoud

Tabelsets via de API

Het concept achter kennis staat in Kennis.

Een tabelset bevat de tabellen in je documenten -- elk werkblad van een werkmap, elk CSV- of JSON-bestand, elke tabel in een Word-bestand, en met het sjabloon tables/llama-parse die van een geparseerde PDF -- elk met zijn kolommen en zijn rijen, zodat je ze op veld kunt bevragen. Een index beantwoordt "welke passages lijken op deze vraag"; een graaf beantwoordt "wat hangt hiermee samen"; een tabelset beantwoordt "welke rijen voldoen aan deze voorwaarden, en wat is hun totaal": de facturen boven 10.000 euro van vorig kwartaal, de omzet per leverancier, het product met de laagste voorraad.

Je sleutel heeft de capability knowledge nodig. Een tabelset heeft geen eigen capability. Hetzelfde doe je met de muis in Tabelsets in de console.

Tabellen, kolommen en bronnen

Wat het is
Tabelset selecteert een of meer collecties en bevat de tabellen die in hun bronnen staan. Het id begint met tset_
Tabel één tabel uit één bron: een werkblad, de inhoud van een CSV- of JSON-bestand, of een tabel in een document. Het id begint met tbl_
Kolom een veld van een tabel, met een type dat Core uit de waarden afleidt: boolean, integer, number, date of string
Rij één regel van een tabel, als JSON-object van kolomnaam en waarde
Bron het document waaruit een tabel is gelezen. Een tabel hoort bij precies één bron en verdwijnt als de bron verdwijnt

Core schrijft de tabellen zelf, uit je documenten. Je kunt een rij niet met de hand toevoegen, wijzigen of verwijderen; wijzig het document en Core leest het opnieuw. Voor records die je applicatie zelf schrijft, gebruik je een datastore.

1. Een tabelset maken

curl -s -X POST "$CORE_URL/knowledge/table-sets" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{
      "name": "financiele-tabellen",
      "description": "Facturen, begrotingen en leverancierslijsten uit de map financiën.",
      "collection_ids": [{"collection_id": "col_2Pf7...",
                          "metadata_filter": {"afdeling": "financien"}}],
      "ingest_pipeline": {"template": "tables/documents"},
      "labels": {"afdeling": "financien"}
    }'

Het antwoord is 201 met de tabelset en een Location-header. De voorbeelden hieronder gebruiken $TABLE_SET_ID voor het id.

Veld Wat het doet
name uniek binnen de organisatie: kleine letters, cijfers, _ en -, beginnend met een kleine letter of een cijfer, hoogstens 64 tekens. Een policyregel noemt de tabelset bij deze naam, dus die verandert daarna nooit meer
description wat de tabelset bevat, hoogstens 1.024 tekens
collection_ids hoogstens 16 collecties, elk met een optioneel metadata_filter. Alleen bronnen waarvan de metadata het filter bevat, worden gelezen; zonder filter de hele collectie
ingest_pipeline hoe tabellen worden gelezen: een ingebouwd sjabloon tables/... of een versie van een eigen pipeline (De pipeline kiezen). Laat het weg of stuur null om tables/documents te gebruiken
labels de eigen indeling van je organisatie, als label en waarde; regels over Tabelsets lezen ze
metadata een eigen JSON-object, hoogstens 16 KiB. Core bewaart het en geeft het terug, en leest het nooit

GET /knowledge/table-sets geeft de tabelsets die je sleutel bereikt, GET /knowledge/table-sets/{id} één tabelset. PATCH wijzigt description, labels, metadata, collection_ids en ingest_pipeline; een veld dat je weglaat, blijft zoals het is. De naam kun je niet wijzigen. DELETE verwijdert de tabelset met al zijn tabellen en hun rijen en geeft 204; de collecties en hun bronnen blijven.

Antwoord Betekenis
409 een andere tabelset in je organisatie heeft deze naam al
403 geen regel in de policy van je sleutel staat een tabelset met deze naam toe
422 op labels.<naam> het label staat niet in de vocabulaire van je organisatie
422 op ingest_pipeline de pipeline kan geen tabelset vullen (De pipeline kiezen)

2. Vullen uit bronnen

Een tabelset leest de bronnen van de collecties die hij selecteert. Bij het maken wordt nog niets gelezen: staan er al bronnen in zijn collecties, dan draagt het antwoord reindex_required: true en start je het lezen zelf (Opnieuw lezen). Een bron die je daarna toevoegt, wordt automatisch gelezen.

De pipeline kiezen

Drie ingebouwde sjablonen vullen een tabelset. Geen ervan roept een model aan.

Sjabloon Wat het leest
tables/structured de werkbladen van een werkmap, een CSV- of JSON-bestand, en een bron van het soort records
tables/documents (de standaard) hetzelfde, plus de tabellen in een Word-bestand, een presentatie, een webpagina of Markdown
tables/llama-parse hetzelfde, plus de tabellen van een PDF of een scan, gelezen door de parseerdienst van je installatie. Alleen beschikbaar als je installatie een parseerdienst heeft

Een eigen pipeline mag alleen de stappen input, load, parse, llama_parse, tabular_extract, table_store en output bevatten. Hij heeft minstens één tabular_extract nodig en precies één table_store, die direct naar output leidt. Elke andere pipeline geeft 422 op ingest_pipeline. Een pipeline die een tabelset vult, kan geen index of graaf vullen, en omgekeerd. Zie Pipelines voor het bewerken van stappen; de stap tabular_extract kent sheets (welke werkbladen hij leest) en max_tables (hoogstens zoveel tabellen per bron, standaard 50).

Hoe een tabel wordt gelezen

  • Een werkblad is één tabel, met de eerste rij als kop. Een CSV- of JSON-bestand (een lijst platte objecten) is één tabel. In een Word-bestand, een presentatie, een webpagina of Markdown is elke tabel één tabel, genoemd naar de kop erboven.
  • Elke tabel krijgt zijn eigen kolommen. Core geeft elke kolom het smalste type waar al zijn waarden in passen: boolean, integer, number, date, anders string. Lege cellen tellen niet mee. Een kolom met zowel gehele als decimale getallen is number; elke andere mix is string.
  • Een cel met alleen n/a, na, -, null, none of #n/a geldt als leeg, zodat één plaatshouder een kolom met bedragen niet in tekst verandert.
  • Het scheidingsteken van een CSV wordt herkend: komma, puntkomma, tab of verticale streep.
  • Een getal met een decimale komma, zoals 1.234,56, blijft tekst. Schrijf getallen in een CSV met een decimale punt. Een werkmap bewaart echte getallen en heeft hier geen last van.
  • Een tabel bevat hoogstens 50.000 rijen.

Wanneer een bron wordt gelezen

Een nieuwe bron in een geselecteerde collectie wordt automatisch gelezen. Wijzig je de metadata van een bron, dan bepaalt Core opnieuw in welke tabelsets ze hoort: een tabelset die haar niet meer selecteert, verliest haar tabellen meteen.

GET .../sources geeft per bron de title, de status (queued, processing, ready of failed), table_count en row_count, en bij een fout een vaste error_code. Voeg ?status=failed toe om alleen de mislukte te zien.

error_code Betekenis Wat je doet
parse_error een bestand dat niet te lezen is: een beschadigde werkmap, een CSV met dubbele of lege kolomnamen, een JSON-bestand dat geen array van platte objecten is verbeter het bestand en upload het opnieuw
too_many_tables het document bevat meer tabellen dan max_tables splits het document, of verhoog max_tables in een eigen pipeline
sheet_not_found sheets van de pipeline noemt een werkblad dat de werkmap niet heeft verbeter sheets
too_large een tabel van meer dan 50.000 rijen splits het bestand
too_large_for_pipeline een bestand groter dan de uploadlimiet, standaard 25 MiB; een tabelset leest een document in zijn geheel splits het bestand
table_set_full de tabelset zou meer dan 10.000 tabellen of 5.000.000 rijen bevatten verdeel de collecties over meer tabelsets, of versmal een metadata_filter
no_pipeline de pipelineversie die de tabelset noemt, is verwijderd kies een andere pipeline en lees opnieuw

Andere codes, zoals needs_parse, betekenen hetzelfde als bij een index (Waarom indexeren mislukte).

Een bron zonder tabel, zoals een PDF met alleen lopende tekst, is ready met table_count: 0.

Een nieuwe versie van een document vervangt zijn tabellen. Verandert een bron, dan worden haar tabellen opnieuw gelezen en in hun geheel vervangen: een kolom die verdween, is weg, en een kolom waarvan de waarden van type veranderden, krijgt het nieuwe type. Een tabel op dezelfde plek met dezelfde naam houdt zijn id, dus een bewaard table_id blijft geldig tot het werkblad een andere naam of plek krijgt. Een vraag die een verdwenen kolom noemt, geeft 422 met de naam van die kolom.

Wordt een bron verwijderd, dan verdwijnen haar tabellen en hun rijen mee, in elke tabelset.

Opnieuw lezen

curl -s -X POST "$CORE_URL/knowledge/table-sets/$TABLE_SET_ID/reindex" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"mode": "full"}'

full (de standaard) leest elke bron binnen bereik opnieuw en haalt de tabellen weg van bronnen die de tabelset niet meer selecteert. failed probeert alleen de bronnen opnieuw waarvan het laatste lezen mislukte. Het antwoord is 202 met een run; GET .../runs en GET .../runs/{run_id} tonen de voortgang (status, sources_total, sources_done, sources_failed). Loopt er al een run voor deze tabelset of staat er een in de wachtrij, dan is het antwoord 409.

Lees opnieuw nadat je de tabelset maakte over collecties waar al bronnen in stonden, de pipeline wijzigde, een collectie toevoegde of weghaalde, of een metadata_filter wijzigde. Elk antwoord met de tabelset zegt reindex_required: true tot er een volledige run is afgerond.

3. Een tabel vinden en de rijen lezen

curl -s "$CORE_URL/knowledge/table-sets/$TABLE_SET_ID/tables?q=facturen" \
    -H "Authorization: Bearer $CORE_KEY"

Elke tabel in de lijst heeft een id, een name (de naam van het werkblad, de kop boven de tabel, of table-<n>), een origin (sheet, csv, json of markdown), de position in de bron, columns met hun types, row_count, en de source_id en source_title waaruit hij is gelezen. q houdt de tabellen over waarvan de naam of de titel van de bron de tekst bevat; source_id houdt de tabellen van één bron over. GET .../tables/{table_id} geeft één tabel.

curl -s "$CORE_URL/knowledge/table-sets/$TABLE_SET_ID/tables/tbl_4Hq9.../rows?limit=100" \
    -H "Authorization: Bearer $CORE_KEY"

GET .../rows geeft de rijen in de volgorde van het document, elk als {"id", "data"}, hoogstens 100 per pagina; geef next_cursor mee als cursor voor de volgende pagina.

GET /knowledge/collections/{id}/sources/{sid}/tables geeft de tabellen van één document in elke tabelset die je sleutel bereikt, elk met zijn table_set_id, een pagina tegelijk: limit (1-100, standaard 20), en de next_cursor van een pagina als cursor voor de volgende.

4. Een tabel bevragen

Een vraag is een formulier en geen querytekst. Core controleert hem tegen de kolommen van de tabel en voert hem uit met elke waarde als parameter.

curl -s -X POST "$CORE_URL/knowledge/table-sets/$TABLE_SET_ID/tables/tbl_4Hq9.../query" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{
      "filters": [
        {"field": "land", "op": "in", "value": ["NL", "BE"]},
        {"field": "factuurdatum", "op": "gte", "value": "2026-07-01"}
      ],
      "aggregate": {"fn": "sum", "field": "bedrag"},
      "group_by": "leverancier",
      "sort": {"field": "$value", "direction": "desc"},
      "limit": 10
    }'

Dit leest als: de tien leveranciers met het hoogste totaalbedrag gefactureerd in Nederland of België sinds juli.

Onderdeel Wat je invult
filters hoogstens 20 voorwaarden, waaraan een rij allemaal moet voldoen. Elk is {"field", "op", "value"}
aggregate een van count, sum, avg, min en max, met een field. count mag het veld weglaten en telt rijen; met een veld telt hij de rijen die voor dat veld een waarde hebben. sum en avg vragen een kolom van het type integer of number; min en max werken op elke kolom behalve een boolean
group_by één kolom om de aggregatie op te groeperen. Vraagt een aggregate
sort {"field", "direction"}, direction asc (de standaard) of desc. Zonder aggregatie een kolom. Met group_by de groepskolom of $value, de aggregatie. Een aggregatie zonder group_by geeft één rij en neemt geen sortering
fields de kolommen die elke rij teruggeeft, hoogstens 50; alleen zonder aggregatie. Weggelaten: elke kolom
limit rijen, of groepen, 1 tot 500; standaard 50
cursor de next_cursor van een eerder antwoord op dezelfde vraag, voor de volgende pagina; alleen zonder aggregatie
op Past op
eq, ne, lt, lte, gt, gte een vergelijking, in het type van de kolom. Niet ordenen op een boolean
contains, starts_with, ends_with tekst, letterlijk en zonder onderscheid tussen hoofd- en kleine letters. Alleen tekstkolommen
in een van de waarden in een lijst van hoogstens 100
exists true: de rij heeft een waarde voor de kolom; false: niet

Zonder sort komen rijen in de volgorde van het document en groepen in de volgorde van de groepskolom. Een sortering zet rijen zonder waarde achteraan, in beide richtingen.

Het antwoord, ingekort:

{
  "columns": [{"name": "leverancier", "type": "string"}, {"name": "sum", "type": "number"}],
  "items": [{"leverancier": "Acme", "sum": 48210.5}, {"leverancier": "Widget BV", "sum": 30115.0}],
  "next_cursor": null,
  "truncated": false
}

items bevat de rijen, elk met alleen de gevraagde kolommen, of de groepen, elk met de groepskolom en de aggregatie onder de naam van zijn functie. Een vraag zonder aggregatie blader je door met next_cursor; tussen pagina's mag je limit en fields wijzigen, verder niets. Een gegroepeerd antwoord heeft geen pagina's: zijn er meer groepen dan limit, dan krijg je de eerste limit en truncated: true. Stel dan een smallere vraag in plaats van limit te verhogen.

Antwoord Betekenis
422 met een veld, zoals filters[1].value of sort.field de tabel heeft die kolom niet, de operator past niet bij het type van de kolom, de waarde is niet als dat type te lezen, of de onderdelen passen niet bij elkaar
422 op cursor de cursor hoort bij een andere tabel, andere filters of een andere sortering
422 met de vraag om een smallere vraag de vraag liep over zijn tijdslimiet, standaard vijf seconden; voeg een filter toe
403 de policy van je sleutel vereist een agent; rijen lezen en bevragen werkt alleen binnen een beurt
404 de tabelset of de tabel bestaat niet, of je sleutel of de persoon die het verzoek noemt, bereikt hem niet

Een vraag via deze route wordt niet als gebruik gemeten.

5. Een model laten zoeken

Een model bereikt een tabelset via een zoektool. Kopieer het ingebouwde sjabloon retrieval/table-set, vul het blok table_set in, en registreer de tool op de versie die je terugkrijgt:

curl -s -X POST "$CORE_URL/knowledge/pipelines/from-template" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"template": "retrieval/table-set", "name": "financiele-tabellen-zoeken",
         "sources": {"table_set": "tset_6Rk2..."}}'

curl -s -X POST "$CORE_URL/knowledge/retrieval-tools" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"name": "financiele_tabellen",
         "description": "Beantwoordt vragen over facturen, begrotingen en leveranciers uit de financiële spreadsheets.",
         "pipeline": {"pipeline_id": "pip_...", "version": 1}}'

Bied de tool aan als {"type": "tool", "name": "knowledge__financiele_tabellen"}. Het sjabloon roept zelf geen model aan: het model in de beurt schrijft de vraag.

De tool biedt het model een argument table_query: een table_id, en dezelfde vraag die je stuurt in Een tabel bevragen -- filters met elke operator, aggregate, group_by, sort, fields en limit -- zonder cursor, want een toolaanroep geeft één pagina antwoord. Een kopie van het sjabloon kan de stap tabular_query op de modus llm zetten en een model noemen; dat model stelt dezelfde vraag dan zelf op.

  • De beschrijving van de tool noemt geen tabel. De naam van een tabel, de titel van het document en de kolommen zijn tekst die de auteur van het document schreef, dus de beschrijving bevat alleen id's. Een aanroep zonder table_query krijgt als antwoord de tabellen waar de vraag het waarschijnlijkst over gaat, hoogstens 24, elk met id, naam, documenttitel en kolommen, gerangschikt op de woorden uit de vraag die erin staan. Het model leest die lijst en vraagt opnieuw met een id.
  • Bereikt de tool hoogstens 24 tabellen, dan is table_id een keuze uit hun id's; bereikt hij er meer, dan is het vrije tekst, gecontroleerd als de aanroep loopt. Zo blijft een tabelset met duizenden maandexports bruikbaar.

Het model krijgt de tabel terug, zijn kolommen, de gevonden rijen en de vraag die Core uitvoerde, als gestructureerde gegevens. Rijen voorbij 32.768 tekens JSON vallen weg en het antwoord zegt truncated, zodat het model zijn vraag versmalt in plaats van een gedeeltelijke som als een hele te lezen. Een tabel die de tool niet bereikt en een vraag waar de kolommen niet bij passen, komen bij het model terug als "Unknown table." of als ongeldige argumenten, zodat het zichzelf kan verbeteren; de beurt mislukt er niet door. Een vraag die over zijn tijdslimiet loopt, standaard vijf seconden, komt op dezelfde manier terug als te kostbaar, zodat het model een filter kan toevoegen en opnieuw kan vragen.

Een tabelset kun je op metadata versmallen. Anders dan bij een graaf hoort een tabel bij één bron, dus het metadata_filter en de filter_keys van een tool werken op een tabelset: ze worden vergeleken met de metadata van de bron waaruit elke tabel is gelezen.

Combineer lopende tekst en tabellen in één tool met een eigen pipeline met een blok index en een blok table_set, zodat één tool antwoordt uit zowel de tekst als de tabellen van dezelfde documenten (Eigen zoekpipelines).

Elke tabelvraag in een beurt telt als één retrieval-eenheid (retrieval).

Wie welke tabellen bereikt

Welke tabelsets een sleutel bereikt, bepalen regels over Tabelsets (knowledge_table_set) in de policy, op naam of op label, met de acties see, use, write en delete. De standaardregels van een sleutel die zonder policy is geregistreerd, staan elke tabelset toe. Een tabelset die geen regel toestaat, ontbreekt in lijsten en geeft 404; er een maken onder een naam die geen regel toestaat, geeft 403. Een zoektool waarvan de pipeline een tabelset noemt die je sleutel niet bereikt, kun je niet schrijven of vastzetten, en een verzoek dat hem aanbiedt, wordt geweigerd alsof de tool niet bestaat.

Een policy van een sleutel of organisatie die is opgeslagen voordat er tabelsets bestonden, ook de standaardregels die bij het registreren van een sleutel zijn uitgeschreven, heeft geen regel over Tabelsets en bereikt er geen: opvragen en lezen geven 404 en maken geeft 403, tot je een regel toevoegt die Tabelsets toestaat (Een regel toevoegen of wijzigen).

Binnen een tabelset is een tabel zichtbaar precies wanneer zijn bron dat is. Een verzoek dat met de header X-Core-Subject een persoon noemt (Bouwen voor personen), ziet de tabellen van de eigen bronnen van die persoon, van gedeelde bronnen, en van bronnen die de regels over Kennisbronnen en Opgeslagen bestanden op label toestaan. Dat geldt voor lijsten, rijen, vragen en de toolaanroepen van een model. Een tabel uit de spreadsheet van een ander geeft 404.

Wat een tabelset niet doet

  • Een vraag leest één tabel. Hij koppelt geen tabellen en voegt twaalf maandexports niet samen tot één tabel, ook niet als hun kolommen gelijk zijn.
  • De kop is altijd de eerste rij. Een werkblad dat begint met een titelblok, of een kop over twee rijen heeft, wordt gelezen met de eerste rij als kolomnamen.
  • Getallen met een decimale komma worden als tekst gelezen.
  • Een model kan tijdens een beurt geen rijen schrijven, en via de API kan dat ook niet.
  • Een tabelset kan niet in een publicatie.
  • Een export van de organisatie bevat de tabelset en zijn collecties, niet zijn tabellen: een volledige herindexering op de nieuwe plek leest ze opnieuw.