Citeam recent postarea pe blog a lui Davis Vaughan Semi-automating 200 Pull Requests with Claude Code și a rezonat cu adevărat în mine, deoarece folosesc LLM-uri pentru sarcini obositoare de genul acesta de ceva timp. Perspectiva cheie a lui Davis: structura = succes. Atunci când puteți defini o sarcină în mod strâns și oferi un context clar, LLM-urile devin instrumente cu adevărat utile.
Dacă mi-ați urmărit munca, știți că conductele reproductibile sunt obiectivul meu principal de ceva timp. Acesta este motivul pentru care am scris {rix} pentru medii R reproductibile, {rixpress} pentru conducte declarative și chiar și un port Python numit ryxpress. Cred cu adevărat că aceste instrumente fac știința datelor mai bună: mai reproductibilă, mai depanată, mai ușor de partajat.
Dar știu și că este greu să-i faci pe oameni să adopte noi instrumente. Învățarea unui nou mod de structurare a codului necesită timp și efort, iar majoritatea oamenilor sunt deja suficient de ocupați. Aici intră în imagine LLM-urile: vă pot ajuta să traduceți scripturile dvs. existente în acest format mai structurat. Dumneavoastră furnizați scriptul dvs. monolitic, explicați ce doriți, iar LLM face munca groaznică de a-l restructura.
Modul tipic în care scriem scripturi de analiză (lanțuri lungi de %>% apelurile în R sau înlănțuirea metodei în Python) funcționează bine pentru explorarea interactivă, dar se transformă rapid în spaghete greu de modificat, testat sau depanat. Luați ca exemplu vechea mea analiză a aeroportului din Luxemburg: funcționează, dar transformarea acestui tip de script într-o conductă adecvată cu stocare în cache, dependențe explicite și testabilitate este o muncă plictisitoare.
Dar suntem în 2026, când LLM-urile fac acest lucru banal.
De la implicit la explicit: traducerea unui script într-o conductă rixpress
Permiteți-mi să vă arăt ce vreau să spun prin a traduce acel vechi cod de curățare a datelor din Aeroportul din Luxemburg într-un {rixpress} conductă. Scriptul original folosește continuu %>% înlănțuire, stil tidyverse standard. The {rixpress} versiunea explică fiecare pas intermediar.
Dar iată chestia: LLM-urile nu știu despre {rixpress} scos din cutie. Este un pachet relativ nou și nu este în datele lor de antrenament (sau dacă este, este depășit). Așa că am scris un alt instrument numit pkgctx care extrage specificațiile API structurate, pregătite pentru LLM, din pachetele R sau Python (și ghiciți ce, nu am scris o singură linie de cod din acel instrument, ci LLM-urile au scris).
eu: Vreau să folosesc un pachet numit rixpress, dar nu sunt familiarizat cu el. Mă veți ajuta și aici este un fișier cu o descriere detaliată a API-ului, pentru context.
(încărcat rixpress.ctx.yaml)
LLM: Am înţeles. Voi folosi această specificație API ca referință autorizată pentru rixpress și vă ajută să scrieți conducte corecte, idiomatice cu el.
Câteva puncte de orientare pentru a seta cadrul (încă nu este necesară nicio acțiune):
- rixpress este declarativ: tu descrii derivatii (
rxp_r(),rxp_py(),rxp_qmd()…), apoi materializează-le curxp_populate()şirxp_make().- Datele circulă prin DAG prin artefacte serializate (RDS / pickle / Julia Serialization), nu prin obiecte în memorie.
- Granițele lingvistice sunt explicite (
rxp_r2py(),rxp_py2r()).- Nix este motorul de execuție;
default.nix(sau echivalente) definesc mediul de rulare.Când ești gata, spune-mi ce vrei să faci (…)
Apoi am întrebat pur și simplu:
eu: Ajutați-mă să traduc acest script R într-o conductă rixpress: (am lipit vechiul script)
Și așa am obținut un lucru {rixpress} conductă. LLM a făcut restructurarea plictisitoare; Am revizuit rezultatul, am făcut modificări minore și am terminat. Combinația de pkgctx pentru context și o sarcină clară („traduceți acest script”) au făcut ca LLM să fie cu adevărat util.
Acum să ne uităm la cum arată conducta tradusă. În primul rând, să presupunem:
- Fișierul de date
avia_par_lu.tsvse află în directorul de proiect - Pachetele R necesare sunt disponibile prin
default.nix(vom folosi și un LLM pentru acesta) - Proiectul a fost initializat cu
rxp_init()(acest lucru setează două fișiere schelet pentru a începe rapid)
Faceți clic pentru a extinde întreaga conductă rixpress
library(rixpress)
# Step 0: Load the data
avia <- rxp_r_file(
name = avia,
path = "avia_par_lu.tsv",
read_function = readr::read_tsv
)
# Step 1: Select and reshape (wide → long)
avia_long <- rxp_r(
name = avia_long,
expr =
avia %>%
select("unit,tra_meas,airp_pr\\time", contains("20")) %>%
gather(date, passengers, -`unit,tra_meas,airp_pr\\time`)
)
# Step 2: Split composite key column
avia_split <- rxp_r(
name = avia_split,
expr =
avia_long %>%
separate(
col = `unit,tra_meas,airp_pr\\time`,
into = c("unit", "tra_meas", "air_pr\\time"),
sep = ","
)
)
# Step 3: Recode transport measure
avia_recode_tra_meas <- rxp_r(
name = avia_recode_tra_meas,
expr =
avia_split %>%
mutate(
tra_meas = fct_recode(
tra_meas,
`Passengers on board` = "PAS_BRD",
`Passengers on board (arrivals)` = "PAS_BRD_ARR",
`Passengers on board (departures)` = "PAS_BRD_DEP",
`Passengers carried` = "PAS_CRD",
`Passengers carried (arrival)` = "PAS_CRD_ARR",
`Passengers carried (departures)` = "PAS_CRD_DEP",
`Passengers seats available` = "ST_PAS",
`Passengers seats available (arrivals)` = "ST_PAS_ARR",
`Passengers seats available (departures)` = "ST_PAS_DEP",
`Commercial passenger air flights` = "CAF_PAS",
`Commercial passenger air flights (arrivals)` = "CAF_PAS_ARR",
`Commercial passenger air flights (departures)` = "CAF_PAS_DEP"
)
)
)
# Step 4: Recode unit
avia_recode_unit <- rxp_r(
name = avia_recode_unit,
expr =
avia_recode_tra_meas %>%
mutate(
unit = fct_recode(
unit,
Passenger = "PAS",
Flight = "FLIGHT",
`Seats and berths` = "SEAT"
)
)
)
# Step 5: Recode destination
avia_recode_destination <- rxp_r(
name = avia_recode_destination,
expr =
avia_recode_unit %>%
mutate(
destination = fct_recode(
`air_pr\\time`,
`WIEN-SCHWECHAT` = "LU_ELLX_AT_LOWW",
`BRUSSELS` = "LU_ELLX_BE_EBBR",
`GENEVA` = "LU_ELLX_CH_LSGG",
`ZURICH` = "LU_ELLX_CH_LSZH",
`FRANKFURT/MAIN` = "LU_ELLX_DE_EDDF",
`HAMBURG` = "LU_ELLX_DE_EDDH",
`BERLIN-TEMPELHOF` = "LU_ELLX_DE_EDDI",
`MUENCHEN` = "LU_ELLX_DE_EDDM",
`SAARBRUECKEN` = "LU_ELLX_DE_EDDR",
`BERLIN-TEGEL` = "LU_ELLX_DE_EDDT",
`KOBENHAVN/KASTRUP` = "LU_ELLX_DK_EKCH",
`HURGHADA / INTL` = "LU_ELLX_EG_HEGN",
`IRAKLION/NIKOS KAZANTZAKIS` = "LU_ELLX_EL_LGIR",
`FUERTEVENTURA` = "LU_ELLX_ES_GCFV",
`GRAN CANARIA` = "LU_ELLX_ES_GCLP",
`LANZAROTE` = "LU_ELLX_ES_GCRR",
`TENERIFE SUR/REINA SOFIA` = "LU_ELLX_ES_GCTS",
`BARCELONA/EL PRAT` = "LU_ELLX_ES_LEBL",
`ADOLFO SUAREZ MADRID-BARAJAS` = "LU_ELLX_ES_LEMD",
`MALAGA/COSTA DEL SOL` = "LU_ELLX_ES_LEMG",
`PALMA DE MALLORCA` = "LU_ELLX_ES_LEPA",
`SYSTEM - PARIS` = "LU_ELLX_FR_LF90",
`NICE-COTE D'AZUR` = "LU_ELLX_FR_LFMN",
`PARIS-CHARLES DE GAULLE` = "LU_ELLX_FR_LFPG",
`STRASBOURG-ENTZHEIM` = "LU_ELLX_FR_LFST",
`KEFLAVIK` = "LU_ELLX_IS_BIKF",
`MILANO/MALPENSA` = "LU_ELLX_IT_LIMC",
`BERGAMO/ORIO AL SERIO` = "LU_ELLX_IT_LIME",
`ROMA/FIUMICINO` = "LU_ELLX_IT_LIRF",
`AGADIR/AL MASSIRA` = "LU_ELLX_MA_GMAD",
`AMSTERDAM/SCHIPHOL` = "LU_ELLX_NL_EHAM",
`WARSZAWA/CHOPINA` = "LU_ELLX_PL_EPWA",
`PORTO` = "LU_ELLX_PT_LPPR",
`LISBOA` = "LU_ELLX_PT_LPPT",
`STOCKHOLM/ARLANDA` = "LU_ELLX_SE_ESSA",
`MONASTIR/HABIB BOURGUIBA` = "LU_ELLX_TN_DTMB",
`ENFIDHA-HAMMAMET INTERNATIONAL` = "LU_ELLX_TN_DTNH",
`ENFIDHA ZINE EL ABIDINE BEN ALI` = "LU_ELLX_TN_DTNZ",
`DJERBA/ZARZIS` = "LU_ELLX_TN_DTTJ",
`ANTALYA (MIL-CIV)` = "LU_ELLX_TR_LTAI",
`ISTANBUL/ATATURK` = "LU_ELLX_TR_LTBA",
`SYSTEM - LONDON` = "LU_ELLX_UK_EG90",
`MANCHESTER` = "LU_ELLX_UK_EGCC",
`LONDON GATWICK` = "LU_ELLX_UK_EGKK",
`LONDON/CITY` = "LU_ELLX_UK_EGLC",
`LONDON HEATHROW` = "LU_ELLX_UK_EGLL",
`LONDON STANSTED` = "LU_ELLX_UK_EGSS",
`NEWARK LIBERTY INTERNATIONAL, NJ.` = "LU_ELLX_US_KEWR",
`O.R TAMBO INTERNATIONAL` = "LU_ELLX_ZA_FAJS"
)
)
)
# Step 6: Final cleaned dataset
avia_clean <- rxp_r(
name = avia_clean,
expr =
avia_recode_destination %>%
mutate(passengers = as.numeric(passengers)) %>%
select(unit, tra_meas, destination, date, passengers)
)
# Step 7: Quarterly arrivals
avia_clean_quarterly <- rxp_r(
name = avia_clean_quarterly,
expr =
avia_clean %>%
filter(
tra_meas == "Passengers on board (arrivals)",
!is.na(passengers),
str_detect(date, "Q")
) %>%
mutate(date = yq(date))
)
# Step 8: Monthly arrivals
avia_clean_monthly <- rxp_r(
name = avia_clean_monthly,
expr =
avia_clean %>%
filter(
tra_meas == "Passengers on board (arrivals)",
!is.na(passengers),
str_detect(date, "M")
) %>%
mutate(date = ymd(paste0(date, "01"))) %>%
select(destination, date, passengers)
)
# Populate and build the pipeline
rxp_populate(
list(
avia,
avia_long,
avia_split,
avia_recode_tra_meas,
avia_recode_unit,
avia_recode_destination,
avia_clean,
avia_clean_quarterly,
avia_clean_monthly
)
)
rxp_make()
Acum, aceasta este o „traducere” fidelă a scenariului într-un {rixpress} cu toate acestea, datele originale nu mai sunt acum disponibile, iar seturile de date recente s-au schimbat ușor, ceea ce înseamnă că acest script ar avea nevoie de adaptare suplimentară la sursa de date actuală. Altfel, asta ar fi! Puteți vizualiza scriptul actualizat aici (de asemenea, am eliminat toate recodificarea factorilor, deoarece pare să fie ceva în neregulă cu modul în care {rixpress} mânere `, așa că scrierea acestei postări pe blog mă ajută să găsesc ceva de reparat!)
Generarea mediului
LLM a identificat corect pachetele necesare din codul conductei:
readr(pentruread_tsv)dplyr(pentruselect,filter,mutate,%>%)tidyr(pentrugather,separate)forcats(pentrufct_recode)lubridate(pentruyq,ymd)stringr(pentrustr_detect)rixpress(pentru conducta în sine)
Și am produs acest scenariu:
library(rix)
rix(
date = "2026-01-10",
r_pkgs = c(
"readr",
"dplyr",
"tidyr",
"forcats",
"lubridate",
"stringr",
"rixpress"
),
ide = "none",
project_path = ".",
overwrite = TRUE
)
Există o singură problemă cu acel script: data selectată nu este validă, ar trebui să fie 12 ianuarie. Dar asta este de fapt vina mea: LLM nu avea de unde să știe asta. Singurul mod în care ar fi putut ști este dacă i-aș fi spus să se uite la fișierul csv care listează toate datele valabile pe {rix}depozitul lui. Dar după schimbarea datei, devine posibil să rulați acest script nix-build să construiască mediul şi nix-shell să cadă în ea. De acolo, rulați conducta.
Ceea ce am făcut aici este să folosim LLM-urile la fiecare pas:
- Am dat context despre rixpress (prin
pkgctx) și a cerut LLM să traducă vechiul meu script într-un pipeline - Am dat context despre Rix (prin
pkgctx) și a cerut LLM să genereze configurarea mediului
Modelul este întotdeauna același: context + sarcină în domeniul de aplicare = rezultat util.
Structura + context = munca groaznică externalizată
Ideea pe care o spun aici nu este chiar despre {rixpress} conducte în special. Este vorba despre un principiu mai larg pe care l-am observat atât eu cât și Davis Vaughan: LLM-urile sunt cu adevărat utile atunci când le oferiți suficientă structură și context.
Arhive Davis pre-clonate, pre-generate .Rprofile fișiere și liste de sarcini pre-create, astfel încât Claude să se poată concentra mai degrabă pe corecțiile reale decât pe managementul git. am folosit pkgctx pentru a oferi LLM o specificație API completă și a furnizat un punct de plecare clar (vechiul meu script). În ambele cazuri, formula este aceeași:
Structură + Context → Sarcină în domeniu → LLM poate ajuta de fapt
Am mai scris despre cum poti externaliza munca groaznica catre un LLM, dar nu expertiza. Același lucru este valabil și aici. Mai trebuia să știu ce transformări de date aveam nevoie. Mai trebuia să revizuiesc rezultatul și să fac ajustări. Dar restructurarea plictisitoare (transformarea unui script monolitic într-o conductă declarativă) este exact genul de lucru pe care îl pot gestiona LLM-urile dacă le configurați corect.
Dacă doriți ca LLM-urile să vă ajute în activitatea de știință a datelor:
- Dați-le context. Folosiți instrumente precum
pkgctxpentru a le furniza specificațiile API. Lipiți codul existent. Arată-le exemple. - Scopul sarcinii este bine. „Traduceți acest script într-o conductă rixpress” este o sarcină bine definită. „Fă-mi codul mai bun” nu este.
- Examinați rezultatul. LLM-urile fac muncă groaznică; oferi expertiza.
Dacă nu ești familiarizat cu {rixpress}consultați postarea mea de anunț sau postarea de lansare CRAN. Și dacă doriți să oferiți context LLM-urilor despre pachetele R sau Python, pkgctx este acolo pentru a vă ajuta. Pentru cei care doresc să se scufunde mai adânc în Nix, {rix}și {rixpress}am trimis recent o lucrare la Journal of Statistical Software, pe care o puteți citi aici. Pentru mai multe exemple de {rixpress} conducte, consultați depozitul rixpress_demos.
LLM-urile nu merg nicăieri: geniul a ieșit din sticlă. Încă văd o mulțime de oameni online care susțin că LLM-urile nu sunt utile, dar cred cu adevărat că se reduce la unul din două lucruri:
- Nu oferă suficient context sau nu își definesc sarcinile suficient de bine.
- Au o obiecție de principiu față de LLM, AI și automatizare în general, care, ok, orice, dar nu este un argument tehnic despre utilitate.
Unii oameni ar putea spune chiar că pentru a se simți bine cu ei înșiși: ceea ce programez este mult prea complex și important pentru ca simple LLM-uri să mă poată ajuta. Ok, poate, dar nu toți lucrăm pentru NASA sau altceva. Voi continua să externalizez munca plictisitoare la LLM.

