Çalıştırmalar
Bir çalıştırma; bir satır, bir imleç, denemelerin günlüğü, bir dizi süre sonu ve bir dizi askıya almadır. Bu sayfa, ona ne yapabileceğinizi ve onun size ne söylediğini anlatır.
Çalıştırma başlatmak
Bir çalıştırma bir tanım kimliği ve bir girdiyle başlar. Tanımın bir şeması varsa daemon girdiyi ona göre doğrular, saklanan en yüksek sürümü sabitler, çalıştırmayı ve trace'ini oluşturur ve çalıştırmayı saklandığı hâliyle döndürür. Bundan sonrası çalıştırmanın kendi işidir: daemon adımlarını worker'lara sunar, uykularını ve beklemelerini yürütür, olan biteni günlüğe yazar.
başlatma, yanıtlama, izleme
// Start a run from anywhere that holds an execute key, answer the wait from a
// webhook, then follow the journal to the end and read the run.
const run = await mcpd.runs.start({
definitionId: 'support_ticket',
input: { ticket: 'Q-42' },
})
await mcpd.events.publish({
event: 'approved',
key: 'Q-42',
payload: { by: 'ops' },
eventId: 'hook-1', // a repeat of this id is a duplicate, not a second event
})
for await (const event of mcpd.runs.watch(run.runId)) {
console.log(event.seq, event.kind)
}
const detail = await mcpd.runs.get(run.runId)
console.log(detail.run.status, detail.run.output) # Start a run from anywhere that holds an execute key, answer the wait from a
# webhook, then follow the journal to the end and read the run.
run = mcpd.runs.start(support, input={"ticket": "Q-42"})
# A repeat of this event id is a duplicate, not a second event.
mcpd.events.publish("approved", "Q-42", payload={"by": "ops"}, event_id="hook-1")
for event in mcpd.runs.watch(run.run_id):
print(event.seq, event.kind)
detail = mcpd.runs.get(run.run_id)
print(detail.run.status, detail.run.output) // Start a run from anywhere that holds an execute key, answer the wait from
// a webhook, then follow the journal to the end and read the run.
let start = StartRun::new("support_ticket").input(json!({ "ticket": "Q-42" }));
let run = mcpd.start_run(start).await?;
// A repeat of this event id is a duplicate, not a second event.
let payload = json!({ "by": "ops" });
let approval = PublishEvent::new("approved", "Q-42").payload(payload).event_id("hook-1");
mcpd.publish_event(approval).await?;
mcpd.watch_run(&run.run_id, -1, |e| println!("{} {:?}", e.seq, e.kind)).await?;
let detail = mcpd.get_run(&run.run_id).await?;
println!("{:?} {:?}", detail.run.status, detail.run.output); // Start a run from anywhere that holds an execute key, answer the wait from
// a webhook, then follow the journal to the end and read the run.
run, err := m.StartRun(ctx, mcpd.StartRun{
Definition: "support_ticket",
Input: map[string]any{"ticket": "Q-42"},
})
if err != nil {
panic(err)
}
// A repeat of this event id is a duplicate, not a second event.
_, err = m.PublishEvent(ctx, mcpd.PublishEvent{
Event: "approved",
Key: "Q-42",
Payload: map[string]any{"by": "ops"},
EventID: "hook-1",
})
if err != nil {
panic(err)
}
_ = m.WatchRun(ctx, run.RunID, -1, func(e mcpd.RunEvent) error {
fmt.Println(e.Seq, e.Kind)
return nil
})
detail, _ := m.GetRun(ctx, run.RunID)
fmt.Println(detail.Run.Status, string(detail.Run.Output)) // Start a run from anywhere that holds an execute key, answer the wait from
// a webhook, then follow the journal to the end and read the run.
val run = mcpd.startRun("support_ticket", input = mapOf("ticket" to "Q-42"))
// A repeat of this event id is a duplicate, not a second event.
mcpd.publishEvent("approved", "Q-42", payload = mapOf("by" to "ops"), eventId = "hook-1")
mcpd.watchRun(run.runId) { event -> println("${event.seq} ${event.kind}") }
val detail = mcpd.getRun(run.runId)
println("${detail.run.status} ${detail.run.output}") # Start a run from anywhere that holds an execute key, answer the wait from a
# webhook, then follow the journal to the end and read the run.
{:ok, run} = Mcpd.start_run(mcpd, support, input: %{"ticket" => "Q-42"})
# A repeat of this event id is a duplicate, not a second event.
{:ok, _} =
Mcpd.publish_event(mcpd, "approved", "Q-42",
payload: %{"by" => "ops"},
event_id: "hook-1"
)
mcpd |> Mcpd.watch_run(run.run_id) |> Enum.each(&IO.inspect({&1.seq, &1.kind}))
{:ok, detail} = Mcpd.get_run(mcpd, run.run_id)
IO.inspect({detail.run.status, detail.run.output})
Yeniden denenebilecek her çağrıdan bir idempotency_key
gönderin. Çalıştırma tutulduğu sürece proje içinde benzersizdir ve aynı
çağrının yeniden denenmesi ikinci bir çalıştırma başlatmak yerine aynı
çalıştırmayı döndürür. İki kez tetiklenen bir webhook ya da yeniden başlayan
bir iş çalıştırıcısı böylece hiçbir şeye mal olmaz.
The cursor
The daemon holds the run between steps: where it is, what each step answered, what it is waiting for and until when. A worker holds one step at a time, and only while it runs. During a wait no process is alive at all.
Günlük
Bir çalıştırmadaki her değişiklik, sıfırdan başlayan ve boşluk bırakmayan bir sıra numarası taşıyan bir olaydır: çalıştırma başladı, imleç ilerledi, bir adım için lease verildi, adım başladı, bitti ya da başarısız oldu, çalıştırma bekledi ve kaldığı yerden devam etti, çalıştırma sona erdi. Her biri türüne göre biçimlenmiş bir payload taşır. Günlük, çalıştırmanın geçmişidir ve arayüzün çizdiği şeydir.
Günlüğü after ile sayfa sayfa okuyun, ya da olaylar endpoint'ini
Accept: text/event-stream ile açık tutup her olayı işlendiği anda
alın. Durup yeniden başlayan bir istemci gördüğü son sıra numarasını gönderir
ve hiçbir şeyi kaçırmaz. Her SDK'nın watch çağrısı bunu sizin
için yapar ve çalıştırma bittiğinde biter; wait ise günlüğü
sonuna kadar izler ve çıktıyı döndürür; çalıştırma başarılı olmadıysa
çalıştırmayı taşıyan, türü belli bir hata döndürür.
Olaylar ve beklemeler
Bir wait_event adımı, kendi adını ve işlenmiş anahtarını taşıyan
bir olay gelene kadar çalıştırmayı tutar. Olay tek bir kapıdan girer:
bir beklemeyi yanıtlama
curl -X POST http://localhost:4317/v1/api/events \
-H 'content-type: application/json' \
-d '{"project":"support","event":"approved","key":"Q-42","payload":{"by":"ops"},"event_id":"hook-1"}'
Yanıt üç sonuçtan biridir. delivered: bir çalıştırma bekliyordu
ve olayı aynı işlem içinde aldı, yani siz alındıyı okuduğunuzda çalıştırma
çoktan ilerlemiştir. parked: bekleyen bir çalıştırma yoktu ve
olay, sonradan açılıp park edilmiş olayları kabul ettiğini söyleyen bir
bekleme için saklanır. duplicate: event_id daha
önce görülmüştür ve hiçbir şey yazılmamıştır. İki kez tetiklenebilecek her
şeyden kendi kimliğinizi gönderin.
Süre sonuna ulaşan bir bekleme, on_timeout ne diyorsa onu yapar:
başarısız olur ve sonra on_error karar verir, ya da çıktı
olmadan devam eder. Tek bir çalıştırmaya kendi açık beklemesi üzerinden
doğrudan göndermek için POST /v1/api/runs/{run}/signal
kullanılır; adres çalıştırmanın kendisi olduğu için aynı gövdeyi anahtarsız
alır.
Çalıştırmayı iptal etmek
POST /v1/api/runs/{run}/cancel bir çalıştırmayı ve altındaki
her alt çalıştırmayı sona erdirir. Yürümekte olan bir adım hat üzerinde
kesilmez: worker bir sonraki heartbeat'inde çalıştırmanın bittiğini öğrenir,
bağlamı gövdeye haber verir ve gövdenin bundan sonra gönderdiği her şey
run_terminal ile reddedilir. Zaten sona ermiş bir çalıştırmayı
iptal etmek hiçbir şeyi değiştirmez ve çalıştırmayı olduğu gibi döndürür.
Zamanlayıcılar ve sınırlar
Uykular, bekleme süre sonları, yeniden deneme beklemeleri ve lease bitişleri;
hepsi daemon'un tuttuğu zamanlayıcılardır. Daemon'un saatiyle, saniyede bir
tetiklenirler; bu yüzden yüz milisaniyelik bir retry_after_ms
tam olarak o kadar değil, en az o kadar diye uygulanır. Daemon'un yeniden
başlaması hiçbirini kaybettirmez; hepsi birer satırdır ve çalıştırma satırdan
devam eder. Hiçbir süre 3650 günden uzun olamaz ve bir worker'dan gelen daha
uzun bir retry_after_ms bir 400 ile reddedilir. 2262 yılından
sonraki bir sleep_until, hiçbir zaman gelmeyecek bir an olarak
tutulur.
Bir tanımın max_steps, run_timeout_ms ve
max_cost_usd değerleri çalıştırmayı tavanda sona erdirir ve hata
olarak tavanın adını verir. Maliyet, adımların bildirdiği kullanımın
fiyatlanmış toplamıdır; span'leri fiyatlayan kitabın aynısı kullanılır, yani
token'larını bildiren bir ajan adımı, tamamlandığı anda çalıştırmanın
bütçesinden düşülür.
Çalıştırmanın trace'indeki yeri
Her çalıştırma aynı zamanda bir trace'tir. Daemon çalıştırma için bir kök
span, her deneme için de adımın girdisi ve çıktısıyla birer span açar; adım
bağlamı üzerinden kendi span'lerini kaydeden bir gövdenin span'leri de
bunların altına girer. Böylece bir ajan adımının içindeki model çağrısı o
adımın, o adım da o çalıştırmanın altında durur; herhangi bir trace
üzerinde çalışan arama, maliyet muhasebesi ve puanlayıcılar bir
çalıştırmanın trace'inde de değişmeden çalışır. Bu span'ler, bir SDK'nın
span'lerinin geçtiği alım yolunun aynısından yazılır; bu yüzden aynı
biçimde örneklenir, maskelenir, fiyatlanır ve iletilir. Token'lar onları
harcayan model span'lerinde durur; adım span'i fiyatlanmış toplamı özniteliklerinde
taşır, böylece bir trace toplamı her
token'ı bir kez sayar. Çalıştırma kendi trace_id değerini taşır
ve arayüz ikisini birbirine bağlar.
Daemon o span'leri, onları doğuran satır işlendikten sonra kendi saatiyle bir giden kutusu üzerinden yazar. Bir adım tamamlandıktan hemen sonra okunan bir trace o adımın span'ini henüz taşımıyor olabilir; çalıştırmanın kendi ayrıntısı her zaman taşır.
Çalıştırmaları geri okumak
GET /v1/api/runs bir projenin çalıştırmalarını, en son
güncellenen başta olacak şekilde, duruma ya da tanıma göre filtreleyip
listeler. GET /v1/api/runs/{run} ise çalıştırmanın kendisi ve
onunla ilgili kalıcı olan her şeydir: lease sahibi, zamanları, hatası ve
kullanımıyla her deneme; hâlâ bekleyen zamanlayıcılar; hâlâ açık olan
beklemeler. Arayüzün
İş akışları ekranı
bu endpoint'in çizilmiş hâlidir, yanında da günlük canlı akar.
İkisi de çalıştırmaya verileni geri döndürür, bu yüzden ikisi de denetim
günlüğüne list_runs ve get_run olarak kaydedilir.
Çalıştırmanın günlüğü (list_run_events, istek başına bir kez,
bir akış da dahil), bir adımın nedenini geriye doğru izleyen çağrı
(get_step_cause) ve bir iptal (cancel_run) de
kaydedilir. Reddedilen bir okuma, çalıştırmanın sahibi olan projeye karşı
kaydedilir. Worker çağrıları kaydedilmez;
Worker'lar sayfasına
bakın.
Depolama
Çalıştırmalar, trace deposunun yanında kendi SQLite dosyasında durur:
--runs-data başka bir şey söylemedikçe
<data>.runs.<ext>. --runs-data off
çalıştırma yüzeyini tamamen kapatır, o durumda her çalıştırma endpoint'i 503
döndürür. Yedeğini trace deposuyla birlikte alın; mcpd doctor
ona kendi grubu olarak not verir.
Çalıştırma deposu payload'ları gönderildikleri gibi tutar. Bir
çalıştırmanın girdisi, durum verisi, çıktısı ve ilişkilendirmesi, her
adımın girdisi, çıktısı ve hatası, her olay payload'ı ve trace deposuna
yazılmayı hâlâ bekleyen span payload'ları maskelenmeden saklanır; bir
çalıştırmanın ilişkilendirmesindeki user_id da özetlenmeden
saklanır. Bu bilerek böyledir: bunlar iş akışının çalışma durumudur,
sonraki bir adım onları geri okur ve maskeleyicinin yeniden yazdığı bir
değer çalıştırmanın yaptığı şeyi değiştirirdi. Daemon'un trace deposuna
yazdığı span'ler, bir SDK'nın gönderdiği bir span ile aynı maskelemeden ve
tanımlayıcı özetlemesinden geçer, yani trace deposu maskelenmiş kopyayı
tutar. Çalıştırma dosyasını, adımlarınıza verilen her şeyi tutan bir dosya
olarak görün; onu buna göre koruyun ve yedeğini buna göre alın.
Saklama, çalıştırma deposunu kapsar. Trace deposunu budayan saatlik tarama, aynı proje başına pencereyle, kökü kesim noktasından önce sona ermiş her çalıştırmayı da alt çalıştırmalarıyla ve onlar için saklanan her şeyle birlikte siler: denemeler, olaylar, süre sonları, beklemeler, kuyruktaki span'ler ve o çalıştırmaların tükettiği olaylar. Sona ermemiş bir çalıştırma, ne kadar eski olursa olsun hiçbir zaman silinmez. Tanımlar tutulur.
Silme de onu kapsar. DELETE /v1/api/subjects/{user_id},
projede ilişkilendirmesi o user_id değerini, çalıştırmayı
başlatırken verdiğiniz biçimde taşıyan ve sona ermiş her çalıştırmayı,
alt çalıştırmalarının tüm ağacıyla ve onlar için saklanan her şeyle
birlikte siler ve sayıyı runs_erased olarak bildirir. O
kişinin sona ermemiş bir çalıştırması bütün olarak bırakılır,
runs_in_flight içinde sayılır ve yanıtın note
alanında adlandırılır: onu iptal edin ya da bitmesine izin verin ve silme
isteğini yeniden gönderin. Henüz beklemesine ulaşmamış bir çalıştırma için
park edilmiş bir olay bir kişiye göre değil, ilişkilendirme anahtarına göre
anahtarlanır, bu yüzden silme ona erişmez; süresi dolduğunda, 24 saat
sonra silinir. Yanıtın geri kalanı için
Maskeleme ve saklama
sayfasına bakın.
Sonraki
- Bir tanımın neler söyleyebildiği: İş akışları.
- Adımları sunan süreç: Worker'lar.
- Belirtimden üretilen her çalıştırma endpoint'i: HTTP API, Runs.