Skip to content

Write and publish a function#

Below, <org>/<workspace> stands for your organisation and workspace, as astra use set them.

The handler#

A function is one file. The function in it named handler (or the name in spec.handler) is called with the call's input, a JSON value, and what it returns is the answer, as JSON.

weather.py
import requests

def handler(city: str, days: int = 3) -> dict:
    """Get the daily maximum temperature in a city.

    Args:
        city: The city, as people write it.
        days: How many days ahead, 1 to 7.
    """
    print(f"looking up {city}")
    geo = requests.get("https://geocoding-api.open-meteo.com/v1/search",
                       params={"name": city, "count": 1}, timeout=10).json()
    place = geo["results"][0]
    f = requests.get("https://api.open-meteo.com/v1/forecast",
                     params={"latitude": place["latitude"], "longitude": place["longitude"],
                             "daily": "temperature_2m_max", "forecast_days": days,
                             "timezone": "auto"}, timeout=10).json()
    return {"city": place["name"], "dates": f["daily"]["time"],
            "max_c": f["daily"]["temperature_2m_max"]}
  • Named parameters: the input's fields are passed as keyword arguments ({"city": "Lisbon"} → handler(city="Lisbon")). A field the handler does not take is an error, unless it takes **kwargs.
  • The input whole: a handler whose only parameter is event gets the input as it is (an object, a list, a string…).
  • A parameter named context gets the call's context: context.invocation_id, context.function, context.version, and context.remaining_ms().
  • async def handlers work. What is printed goes to the log, each line prefixed with the call's id.
weather.mjs
/**
 * Get the daily maximum temperature in a city.
 * @param {object} event
 * @param {string} event.city - The city, as people write it.
 * @param {integer} [event.days=3] - How many days ahead, 1 to 7.
 */
export async function handler(event, context) {
  console.log(`looking up ${event.city}`);
  const geo = await (await fetch(`https://geocoding-api.open-meteo.com/v1/search?count=1&name=${encodeURIComponent(event.city)}`)).json();
  const place = geo.results[0];
  const q = new URLSearchParams({ latitude: place.latitude, longitude: place.longitude, daily: "temperature_2m_max", forecast_days: event.days ?? 3, timezone: "auto" });
  const f = await (await fetch(`https://api.open-meteo.com/v1/forecast?${q}`)).json();
  return { city: place.name, dates: f.daily.time, max_c: f.daily.temperature_2m_max };
}
  • The handler gets the input whole, and context (invocationId, function, version, remainingMs()).
  • An ES module (export async function handler) or CommonJS (exports.handler = async (event) => …). fetch is built in.
  • What console.log writes goes to the log, each line prefixed with the call's id.

Errors. An exception is the call's answer: 500 with {"error": {"type", "message", "trace"}}. A call that runs past the function's timeout is answered 504 with {"error": {"type": "Timeout"}}; in Python, an instance whose call is still running after twice the timeout is restarted.

Packages#

List them in spec.dependencies, one per entry: pip requirements for Python (requests==2.32.3), npm packages for Node ([email protected]). They are installed each time an instance starts, from the public registries (PyPI, npm). Options (--index-url, -e) are refused. If they do not install, the instance still starts, and every call answers the import error — so you see it at once when you test.

Create it#

astra astraeus functions deploy weather -f weather.py --dep requests==2.32.3 --timeout 20 --http

deploy creates the function, or changes its draft when it exists; the runtime follows the file's extension (.py, .js/.mjs/.cjs).

Astraeus → Functions → New function: a name and a runtime. Write the code in the editor, the packages below it, and Save draft.

curl -sS https://api.astralyx.cloud/v1/functions \
  -H "Authorization: Bearer $ASTRALYX_TOKEN" -H 'content-type: application/json' \
  -d "$(jq -n --rawfile code weather.py '{metadata: {name: "weather"}, spec: {runtime: "python", code: $code, dependencies: ["requests==2.32.3"], timeout_seconds: 20, http: {enabled: true}}}')"
from astralyx import astraeus
c = astraeus.Client.from_config()
c.deploy_function("weather", open("weather.py").read(),
                  dependencies=["requests==2.32.3"], timeout_seconds=20, http={"enabled": True})

Try the draft: Test in the console, or

astra astraeus functions invoke weather@draft -d '{"city": "Lisbon", "days": 2}'

The first call starts an instance (and installs the packages): it takes longer than the next ones.

Settings#

Setting Default Range
timeout_seconds 30 1–900
memory_mb 512 128–65536
cpu_cores 1 1–64 (its share; it may use idle cores beyond)
gpu.count none 1, 2, 4 or 8, with isolation: container
isolation sandbox (gVisor) sandbox, container (weaker)
scaling.concurrency 8 1–1000 calls at once per instance
scaling.min_instances 0 kept warm, for versions aliases name
scaling.max_instances 4 1–64
scaling.queue 64 0–10000 calls waiting
scaling.idle_seconds 300 30–86400
http.enabled false answer at its URL

Credentials are given as variables ({"credential": "github", "env": {"token": "GITHUB_TOKEN"}}) or, with no env, as files at /credentials/<name>/<key>. Variables in env are for what is not secret; names starting ASTRAEUS_ are the platform's. Every field is in the function specification.

Publish and roll out#

Publishing makes the draft the next version, immutable; latest names it at once.

astra astraeus functions publish weather --note "first" --alias prod
astra astraeus functions invoke weather@prod -d '{"city": "Lisbon"}'

Change the code, deploy again, publish v2: latest is v2, prod stays on v1. Roll out by moving prod, and back the same way:

astra astraeus functions alias weather prod v2   # roll out
astra astraeus functions alias weather prod v1   # roll back

Each version an alias names has its own instances: moving an alias starts the new version's (if none runs) and stops those of a version no alias names any more. In the console: Versions → Move.

What a model is told it takes#

Publishing generates the schema of the input from the handler — what a model is told when a deployment uses the function as a tool:

  • Python: each parameter is a property, required unless it has a default (or is Optional[…]/X | None); str, int, float, bool, list[…], dict, Literal[…] (an enum) are understood, anything else is any value. The docstring's first paragraph is the description; its Args: (Google), Parameters (NumPy) or :param x: (Sphinx) entries describe the parameters. A handler taking event whole is described by its docstring's event.<field> entries.
  • Node: the JSDoc block before the handler: `@param {string} event.city
  • …(or a destructured({city})with@param {string} city);[event.days=3]is optional with a default;string,number,integer,boolean,object,T[],'a'|'b'` are understood.

What cannot be read is left open with a warning (astra astraeus functions schema weather, or the editor's side panel). For the example above:

{"description": "Get the daily maximum temperature in a city.",
 "parameters": {"type": "object", "additionalProperties": false, "required": ["city"],
   "properties": {"city": {"type": "string", "description": "The city, as people write it."},
                  "days": {"type": "integer", "default": 3, "description": "How many days ahead, 1 to 7."}}}}