-
Notifications
You must be signed in to change notification settings - Fork 140
Add batch inference pydo and dots examples #1168
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| lang: JavaScript | ||
| source: |- | ||
| import { InferenceClient } from "@digitalocean/dots"; | ||
|
|
||
| const client = new InferenceClient({ | ||
| apiKey: process.env.DIGITALOCEAN_TOKEN, | ||
| }); | ||
|
|
||
| const result = await client.batches.cancel(process.env.BATCH_ID); | ||
|
|
||
| console.log("batch_id: ", result.batch_id); | ||
| console.log("status: ", result.status); | ||
| console.log("cancelled_at:", result.cancelled_at); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| lang: JavaScript | ||
| source: |- | ||
| import { randomUUID } from "node:crypto"; | ||
| import { InferenceClient } from "@digitalocean/dots"; | ||
|
|
||
| const client = new InferenceClient({ | ||
| apiKey: process.env.DIGITALOCEAN_TOKEN, | ||
| }); | ||
|
|
||
| const batch = await client.batches.create({ | ||
| file_id: process.env.BATCH_INPUT_FILE_ID, | ||
| provider: "openai", | ||
| endpoint: "/v1/chat/completions", | ||
| completion_window: "24h", | ||
| request_id: randomUUID(), | ||
| }); | ||
|
|
||
| console.log("batch_id:", batch.batch_id); | ||
| console.log("status: ", batch.status); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| lang: JavaScript | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks right against batch_file_create_response.yml. One nit: client.files.create(...) reads like OpenAI-Files; if the SDK actually exposes this as client.batches.files.create(...) (the URL is /v1/batches/files), prefer that name for clarity. |
||
| source: |- | ||
| import { InferenceClient } from "@digitalocean/dots"; | ||
|
|
||
| const client = new InferenceClient({ | ||
| apiKey: process.env.DIGITALOCEAN_TOKEN, | ||
| }); | ||
|
|
||
| const intent = await client.files.create({ | ||
| file_name: "batch_requests.jsonl", | ||
| }); | ||
|
|
||
| console.log("file_id: ", intent.file_id); | ||
| console.log("upload_url:", intent.upload_url); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| lang: JavaScript | ||
| source: |- | ||
| import { InferenceClient } from "@digitalocean/dots"; | ||
|
|
||
| const client = new InferenceClient({ | ||
| apiKey: process.env.DIGITALOCEAN_TOKEN, | ||
| }); | ||
|
|
||
| const batch = await client.batches.retrieve(process.env.BATCH_ID); | ||
|
|
||
| console.log("batch_id: ", batch.batch_id); | ||
| console.log("status: ", batch.status); | ||
| console.log("request_counts:", batch.request_counts); | ||
| console.log("output_file_id:", batch.output_file_id); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| lang: JavaScript | ||
| source: |- | ||
| import { InferenceClient } from "@digitalocean/dots"; | ||
|
|
||
| const client = new InferenceClient({ | ||
| apiKey: process.env.DIGITALOCEAN_TOKEN, | ||
| }); | ||
|
|
||
| const batchId = process.env.BATCH_ID; | ||
|
|
||
| // client.files.content resolves the result envelope and follows the | ||
| // presigned URL for you, returning the raw fetch Response. | ||
| const resp = await client.files.content(batchId); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Likely wrong API call. client.files.content(batchId) passes a batch id to a files helper. The endpoint GET /v1/batches/{batch_id}/results returns presigned URLs in batch_results_response.yml; you then fetch(output_file_url). Should be: |
||
| if (!resp.ok) { | ||
| throw new Error(`results not ready: HTTP ${resp.status}`); | ||
| } | ||
|
|
||
| const body = await resp.text(); | ||
| const lines = body.split("\n").filter(Boolean); | ||
|
|
||
| console.log(`got ${lines.length} line(s); first entry:`); | ||
| console.log(lines[0]); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| lang: JavaScript | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blocker — wrong pagination shape. Uses page.edges.map(e => e.node) (Relay-style), but batch_list_response.yml is { object, data, has_more, first_id, last_id }. Should be:
|
||
| source: |- | ||
| import { InferenceClient } from "@digitalocean/dots"; | ||
|
|
||
| const client = new InferenceClient({ | ||
| apiKey: process.env.DIGITALOCEAN_TOKEN, | ||
| }); | ||
|
|
||
| const page = await client.batches.list({ limit: 20 }); | ||
|
|
||
| for (const b of page.data ?? []) { | ||
| console.log(`${b.batch_id}\t${b.status}\t${b.created_at}`); | ||
| } | ||
|
|
||
| console.log("has_more:", page.has_more); | ||
| console.log("last_id: ", page.last_id); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| lang: JavaScript | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Combines step 1 (reserve intent) and step 2 (PUT bytes) into one snippet. That's fine but it duplicates create_batch_file. Consider trimming step 1 here so each example documents one endpoint, matching the curl pair. |
||
| source: |- | ||
| // Two-step upload flow: | ||
| // 1. Reserve a file_id + presigned upload_url via client.files.create. | ||
| // 2. PUT the raw JSONL bytes to upload_url. | ||
| // | ||
| // The presigned URL is short-lived (~15 minutes) and signature-sensitive — | ||
| // use it verbatim and prefer Content-Type application/octet-stream (or | ||
| // omit the header entirely). A custom value such as application/jsonl | ||
| // can break signature matching. | ||
| import { readFile } from "node:fs/promises"; | ||
| import { InferenceClient } from "@digitalocean/dots"; | ||
|
|
||
| const client = new InferenceClient({ | ||
| apiKey: process.env.DIGITALOCEAN_TOKEN, | ||
| }); | ||
|
|
||
| // Step 1: reserve the upload slot. | ||
| const intent = await client.files.create({ file_name: "batch_requests.jsonl" }); | ||
|
|
||
| // Step 2: PUT the JSONL bytes to the presigned URL. | ||
| const body = await readFile("batch_requests.jsonl"); | ||
| const res = await fetch(intent.upload_url, { | ||
| method: "PUT", | ||
| headers: { "Content-Type": "application/octet-stream" }, | ||
| body, | ||
| }); | ||
| if (!res.ok) { | ||
| throw new Error(`Upload failed: HTTP ${res.status} ${res.statusText}`); | ||
| } | ||
|
|
||
| console.log("uploaded file_id:", intent.file_id); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| lang: Python | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two blockers. result.get("id") → result.get("batch_id"). |
||
| source: |- | ||
| import os | ||
|
|
||
| from pydo import Client | ||
|
|
||
| client = Client(token=os.environ.get("DIGITALOCEAN_TOKEN")) | ||
|
|
||
| result = client.batches.cancel(os.environ["BATCH_ID"]) | ||
|
|
||
| print("batch_id: ", result.get("batch_id")) | ||
| print("status: ", result.get("status")) | ||
| print("cancelled_at:", result.get("cancelled_at")) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| lang: Python | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blocker — request body doesn't match batch_create_request.yml. input_file_id= should be file_id= (line 8 of the example).
Also batch.get("id") → batch.get("batch_id") per batch.yml:12. |
||
| source: |- | ||
| import os | ||
| import uuid | ||
|
|
||
| from pydo import Client | ||
|
|
||
| client = Client(token=os.environ.get("DIGITALOCEAN_TOKEN")) | ||
|
|
||
| batch = client.batches.create( | ||
| file_id=os.environ["BATCH_INPUT_FILE_ID"], | ||
| provider="openai", | ||
| endpoint="/v1/chat/completions", | ||
| completion_window="24h", | ||
| request_id=str(uuid.uuid4()), | ||
| ) | ||
|
|
||
| print("batch_id:", batch.get("batch_id")) | ||
| print("status: ", batch.get("status")) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| lang: Python | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blocker — wrong endpoint and wrong response shape. Mirror the dots version: call the batch-files create method with file_name=... and print file_id / upload_url. The actual JSONL bytes belong in inference_upload_batch_file.yml, not here. |
||
| source: |- | ||
| import json | ||
| import os | ||
| from pathlib import Path | ||
|
|
||
| from pydo import Client | ||
|
|
||
| client = Client(token=os.environ.get("DIGITALOCEAN_TOKEN")) | ||
|
|
||
| input_path = Path("batch_requests.jsonl") | ||
| requests = [ | ||
| { | ||
| "custom_id": "q-1", | ||
| "method": "POST", | ||
| "url": "/v1/chat/completions", | ||
| "body": { | ||
| "model": "llama3.3-70b-instruct", | ||
| "messages": [ | ||
| {"role": "user", "content": "One fun fact about octopuses."} | ||
| ], | ||
| "max_tokens": 128, | ||
| }, | ||
| }, | ||
| { | ||
| "custom_id": "q-2", | ||
| "method": "POST", | ||
| "url": "/v1/chat/completions", | ||
| "body": { | ||
| "model": "llama3.3-70b-instruct", | ||
| "messages": [ | ||
| {"role": "user", "content": "One fun fact about sharks."} | ||
| ], | ||
| "max_tokens": 128, | ||
| }, | ||
| }, | ||
| ] | ||
| input_path.write_text("\n".join(json.dumps(r) for r in requests) + "\n") | ||
|
|
||
| uploaded = client.files.create(file=input_path, purpose="batch") | ||
|
|
||
| print("file_id: ", uploaded.file_id) | ||
| print("filename:", uploaded.filename) | ||
| print("bytes: ", uploaded.bytes) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| lang: Python | ||
| source: |- | ||
| import os | ||
|
|
||
| from pydo import Client | ||
|
|
||
| client = Client(token=os.environ.get("DIGITALOCEAN_TOKEN")) | ||
|
|
||
| batch = client.batches.retrieve(os.environ["BATCH_ID"]) | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blocker — batch.get("id") is always None. Per batch.yml, the field is batch_id. Change to batch.get("batch_id"). |
||
| print("batch_id: ", batch.get("batch_id")) | ||
| print("status: ", batch.get("status")) | ||
| print("request_counts:", batch.get("request_counts")) | ||
| print("output_file_id:", batch.get("output_file_id")) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| lang: Python | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blocker — wrong field name. Line reads links["output_file_id"], but batch_results_response.yml returns output_file_url (a short-lived presigned URL). The endpoint does not return an output file ID. The follow-up client.files.content(...) call also doesn't compose: you GET the presigned URL with requests.get, you don't pass it through the SDK. Rewrite as:
|
||
| source: |- | ||
| import os | ||
| from pathlib import Path | ||
|
|
||
| import requests | ||
| from pydo import Client | ||
|
|
||
| client = Client(token=os.environ.get("DIGITALOCEAN_TOKEN")) | ||
|
|
||
| batch_id = os.environ["BATCH_ID"] | ||
|
|
||
| links = client.batches.results.retrieve(batch_id) | ||
|
|
||
| if not links.get("result_available"): | ||
| print("results not ready yet; poll batch status and retry") | ||
| raise SystemExit(0) | ||
|
|
||
| resp = requests.get(links["output_file_url"], timeout=60) | ||
| resp.raise_for_status() | ||
|
|
||
| out = Path("batch_output.jsonl") | ||
| out.write_bytes(resp.content) | ||
|
|
||
| print("wrote:", out) | ||
| print("----- preview -----") | ||
| print(resp.text[:500]) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| lang: Python | ||
| source: |- | ||
| import os | ||
|
|
||
| from pydo import Client | ||
|
|
||
| client = Client(token=os.environ.get("DIGITALOCEAN_TOKEN")) | ||
|
|
||
| resp = client.batches.list(limit=20) | ||
|
|
||
| for b in resp.get("data") or []: | ||
| print(f"{b.get('batch_id'):40} {b.get('status'):12} {b.get('created_at')}") | ||
|
|
||
| print("has_more:", resp.get("has_more")) | ||
| print("last_id: ", resp.get("last_id")) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| lang: Python | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Misleading lead comment. Lines 1–4 claim client.files.create() "performs both steps for you, prefer it" — that contradicts your create_batch_file example, which only reserves the intent. Drop the comment or rewrite it to say "step 1 reserves file_id+upload_url (see create_batch_file); this example PUTs the bytes." PUT logic itself looks fine. Minor: avoid printing upload_url-derived state. |
||
| source: |- | ||
| # Two-step upload flow: | ||
| # 1. Reserve a file_id + presigned upload_url via client.batches.files.create. | ||
| # 2. PUT the raw JSONL bytes to upload_url. | ||
| # | ||
| # The presigned URL is short-lived (~15 minutes) and signature-sensitive — | ||
| # use it verbatim and prefer Content-Type application/octet-stream (or | ||
| # omit the header entirely). A custom value such as application/jsonl | ||
| # can break signature matching. | ||
| import os | ||
| from pathlib import Path | ||
|
|
||
| import requests | ||
| from pydo import Client | ||
|
|
||
| client = Client(token=os.environ.get("DIGITALOCEAN_TOKEN")) | ||
|
|
||
| input_path = Path("batch_requests.jsonl") | ||
|
|
||
| # Step 1: reserve the upload slot. | ||
| intent = client.batches.files.create(file_name=input_path.name) | ||
| upload_url = intent["upload_url"] | ||
| file_id = intent["file_id"] | ||
|
|
||
| # Step 2: PUT the JSONL bytes to the presigned URL. | ||
| with input_path.open("rb") as fh: | ||
| put = requests.put( | ||
| upload_url, | ||
| data=fh, | ||
| headers={"Content-Type": "application/octet-stream"}, | ||
| timeout=60, | ||
| ) | ||
| put.raise_for_status() | ||
|
|
||
| print("uploaded file_id:", file_id) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two issues.
result.id → result.batch_id.
result.cancel_requested_at doesn't exist; use cancelled_at or just print status.