Functions and Scaling Groups
Call deployed Chalk Functions with defer() and get(), fan out with gather(), compose nested calls, and handle errors.
This page covers deferred calls, fan-out, and nested calls to a deployed Chalk Function, and how its errors reach the caller.
.remote(...) blocks for the entire round trip. Use
.defer(...) instead when you want to submit a call and collect its result
later; it enqueues the call and returns a FunctionCallHandle immediately,
without waiting for the function to run. This example calls the add function from
Defining a function:
import chalkcompute
add = chalkcompute.RemoteFunction.from_name("add")
handle = add.defer(3, 5)
# ...do other work while the call runs...
result = handle.get()
print(result) # 8
handle.get(timeout=None) blocks until the result is ready and returns the
same value .remote(...) would have returned.
Each handle exposes the server-assigned call_id it was enqueued under:
handle = add.defer(3, 5)
print(handle.call_id)
Because .defer(...) returns immediately, you can enqueue many calls up
front and collect their results afterward. Use chalkcompute.gather(...) to
wait for a batch of handles concurrently and get their results back in order:
import chalkcompute
handles = [classify.defer(text) for text in texts]
results = chalkcompute.gather(handles)
gather(...) waits for the handles concurrently rather than one at a time,
so it returns as soon as the slowest call finishes. Pass timeout to bound the whole wait:
results = chalkcompute.gather(handles, timeout=30.0)
Inside a running event loop, gather(...) returns an awaitable instead of
blocking: await chalkcompute.gather(handles).
Each deferred call is still subject to the function’s own concurrency and rate limit policies, so fanning out this way doesn’t bypass those caps.
Pass timeout (seconds) to bound how long get() waits:
try:
result = handle.get(timeout=5.0)
except TimeoutError:
print("still running")
A timeout only stops waiting locally. It does not cancel the underlying
call, which keeps running and can still be collected with another get().
If the function raised an exception, .get() raises
chalkcompute.FunctionError describing the failure. Transient
infrastructure failures, such as a dropped connection between the queue and
the worker, are retried automatically before get() ever sees them; only
failures from the function body itself, or infra failures that exhaust
retries, are raised to the caller.
A function can call other functions with .remote(...). Each inner call is
dispatched independently, so you can compose pipelines where each step runs on
its own infrastructure. This example calls the transcribe async generator from
Streaming results:
import chalkcompute
@chalkcompute.function
def translate(text: str, target_lang: str) -> str:
from transformers import pipeline
translator = pipeline("translation", model=f"Helsinki-NLP/opus-mt-en-{target_lang}")
return translator(text)[0]["translation_text"]
@chalkcompute.function
async def transcribe_and_translate(audio_uri: str, target_lang: str):
"""Transcribe audio, then translate each segment."""
translate.wait_ready()
async for segment in transcribe.remote(audio_uri):
translated = await translate.remote(segment["text"], target_lang)
yield {
**segment,
"translated": translated,
}
The caller sees a single stream of translated segments:
import asyncio
transcribe_and_translate.wait_ready()
async def main():
async for seg in transcribe_and_translate.remote(
"https://example.com/meeting.wav",
target_lang="es",
):
print(f"[{seg['start']:.1f}s] {seg['translated']}")
asyncio.run(main())
[0.0s] Bienvenidos a la revisión trimestral.
[3.2s] Comencemos con las cifras de ingresos.
[6.1s] El tercer trimestre llegó a 4,2 millones, un 18 por ciento más.
The outer function composes two inner calls: transcribe (async generator)
and translate (single-value). Each runs on its own compute configuration.
If a function raises an exception, the caller receives it as a RuntimeError with the
original exception type and message:
import chalkcompute
@chalkcompute.function
def divide(a: float, b: float) -> float:
return a / b
divide.wait_ready()
try:
divide.remote(1.0, 0.0)
except RuntimeError as e:
print(e) # ZeroDivisionError: division by zero
For async generators, an exception mid-stream terminates the iterator. Any segments already yielded are still available to the caller; only subsequent iteration raises:
import chalkcompute
@chalkcompute.function
async def fragile_gen():
yield "ok-1"
yield "ok-2"
raise ValueError("something went wrong")
import asyncio
async def main():
async for item in fragile_gen.remote():
print(item)
asyncio.run(main())
# ok-1
# ok-2
# RuntimeError: ValueError: something went wrong