Skip to main content

FireSDK (Python)

A small Python client for the Fire AI Inference API — model-agnostic chat/image completions (L1), canned server-side workflows (L2), and a data-driven, resumable multi-agent flow layer (v2). Built for LLM-agent and test-script use: synchronous, minimal dependencies (just requests).

pip install -e .          # from a clone of this repo
from fire_sdk import FireClient

client = FireClient(token="fire_sk_...")
result = client.chat([{"role": "user", "content": "Hello"}])
print(result.content)

1.0.0 (2026-08-03) is a breaking change from 0.x. Result-bearing calls (chat, chat_parallel, image, run_workflow, run_flow and friends) now return typed objects — attribute access (result.content) instead of dict access (result["content"]). Every object still carries the full original response as .raw, so nothing is lost, only the primary access pattern changed. See "Why typed objects" below for the reasoning, and search-and-replace ["x"].x on the fields you use if you're migrating. Discovery calls (capabilities, status, models, usage) and CRUD metadata (agent-configs, flow-definitions) are unaffected — those still return plain dicts.

Getting a token

Ask whoever gave you access to this SDK for a fire_sk_... token. If they gave you a tier code instead, redeem it yourself — no token needed to do this:

from fire_sdk import FireClient

token, response, client = FireClient.redeem_tier_code(
    code="YOUR-CODE", name="Your Name", email="you@example.test"
)
print(token)  # save this — it's shown once

Start here: discover the API

caps = client.capabilities()   # no token required, still a plain dict
print(caps["planes"].keys())   # ('data', 'workflows', 'service_control', 'flows', 'billing', 'governance')
print(caps["models"])          # every active model + its `strengths`
print(caps["account_setup"])   # {"url": ".../portal", ...} — where a human sets up billing

Chat

result = client.chat(
    [{"role": "user", "content": "What is 2+2?"}],
    system_prompt="Be concise.",
    species_name="claude-sonnet-4-5",   # omit for the default model — or use Species, below
    temperature=0.7,
)
print(result.content, result.price_usd)

parallel = client.chat_parallel([
    {"messages": [{"role": "user", "content": "..."}], "species_name": "gpt-4o"},
    {"messages": [{"role": "user", "content": "..."}], "species_name": "claude-sonnet-4-5"},
])
for item in parallel.results:
    print(item.content if item.ok else item.error)

Note: models tagged "reasoning" in their strengths (check client.models()) spend part of their max_tokens budget on hidden reasoning before any visible output — Fire silently raises a too-low max_tokens to a safe floor server-side so you never get billed for an empty response, but if you're passing a very tight budget on purpose, know that it may come back larger than you asked for.

Image

result = client.image("a lighthouse at dawn, painterly americana style", n=1, size="1024x1024")
print(result.images[0].url or result.images[0].b64, result.price_usd)

Species — the model taxonomy, as a navigable registry

Fire organizes every model under a biological taxonomy (family → genus → species). Species is a generated registry (scripts/generate_species.py, run against GET /v1/capabilities, checked in — not fetched at import time) that turns that taxonomy into discoverable, typo-proof constants, one nested class per family:

from fire_sdk import Species

client.chat([...], species_name=Species.Anthropic.CLAUDE_SONNET_4_5)
client.image("...", species_name=Species.Openai.GPT_IMAGE_1)

This is deliberately not a class per model with inheritance down the full family→genus→species chain — the catalog changes constantly (models get onboarded/deprecated/renamed), and a deep class hierarchy would mean this SDK going stale or needing a release every time it does. A generated namespace is the same discoverability without that coupling: regenerate and commit when the catalog changes, nothing more. species_name still takes a plain string too — Species is a convenience, never required.

L2 — workflows

Canned, server-side, multi-step recipes (image, article today — see GET /v1/capabilities's planes.workflows for the current list). Each workflow defines its own request/response contract, so the result is deliberately thin:

result = client.run_workflow("article", source_text="...", seed="...")
print(result.workflow)   # "article"
print(result.raw)        # the workflow's actual (workflow-specific) response

WorkflowResult is a separate root from ChatResult/ImageResult on purpose — L2 is architecturally a different layer server-side (WorkflowContract, not FirePipeline directly), with a contract that varies per workflow slug, not a variant of a one-shot chat/image call.

v2 — multi-agent flows

A flow is a DAG of steps — agent_call (a model call using a named, reusable "agent config": model + temperature + system prompt) and human_gate (pauses the run for a person to weigh in). Flows run asynchronously — starting or resuming one returns immediately with status: "pending"/"running", and you poll (or use .wait()) until it lands on completed, failed, or awaiting_human.

run_flow/get_flow_run/resume_flow_run/wait_for_flow all return a FlowRun — the one typed object in this SDK that owns behavior (.wait(), .resume(), .refresh()) rather than being a frozen snapshot, because a flow run is inherently stateful and pollable, not a one-shot result. FlowRun is its own root too — v2 is a third layer alongside L1 and L2, not a variant of either.

The bundled example: "triad"

A reference flow is already set up on Fire's test1 environment: two "hot" agents (one agreeable, one contrarian) answer your question in parallel, you review both, then a "cool" synthesizer writes the final answer.

FIRE_TOKEN=fire_sk_... python3 examples/triad_example.py "Should remote work be the default?"

Or directly:

run = client.run_flow(flow_slug="triad", input={"prompt_package": "your question here"})
run.wait()   # mutates `run` in place and returns it — `run2 = run.wait()` also works, run2 is run

if run.status == "awaiting_human":
    gate = run.gating_step()
    print(gate.prompt_for_human)
    print("Agreeable:", gate.context["hot_1"])
    print("Contrarian:", gate.context["hot_2"])

    run.resume(gate.step_key, {"note": "your guidance"})
    run.wait()

print(run.output["content"])   # the cool synthesizer's final answer — still a dict, workflow/step output shape varies
print(run.total_cost_usd)      # real, billed cost across all 3 agent calls

Building your own flow

Reusable pieces first:

client.create_agent_config(
    "my-hot-agent", "My Hot Agent",
    species_name=Species.Anthropic.CLAUDE_SONNET_4_5, system_prompt="...", temperature=0.9,
)

Then either save a reusable flow definition (create_flow_definition) or run one inline, without saving anything:

spec = {
    "steps": [
        {"step_key": "a", "kind": "agent_call", "agent_config": "my-hot-agent",
         "depends_on": [], "is_output": True,
         "prompt": {"messages": [{"role": "user", "content": "{{input.question}}"}]}},
    ]
}
run = client.run_flow(flow=spec, input={"question": "..."})

A saved flow defaults to public (discoverable via list_flow_definitions/ GET /v2/flows by anyone, runnable by anyone) — your account owns it either way, and only the owner can edit/deactivate it regardless of visibility. Pass is_public=False to create_flow_definition to keep it private instead. External (paying) accounts are capped at 10 custom flow definitions by default; deactivating one frees a slot.

{{token.path}} inside a step's message content resolves against input.* and any already-completed step's {{step_key.output.content}} / {{step_key.human_input.*}}. See examples/triad_example.py and fire_sdk/client.py's docstrings for the full shape — or just inspect client.get_flow_definition("triad") to see a real one (still a plain dict — flow definitions are metadata/CRUD, not a call result).

Saving bandwidth while polling

Pass verbosity="compact" to run_flow, get_flow_run, or resume_flow_run if you only care about the final answer and don't want every step's full content in every poll response:

run = client.get_flow_run(run_id, verbosity="compact")
# run.steps carry only step_key/kind/status/depends_on/error — no output/context

Nothing about this affects what's actually stored or billed — Fire logs every model call in full regardless; this only trims the HTTP response.

Error handling

Every non-2xx response raises a typed exception carrying Fire's error_code/description (or Laravel's default validation shape) as .response:

from fire_sdk import FireAuthError, FireBillingError, FireConflictError, FireValidationError

try:
    client.resume_flow_run(run_id, "wrong_step", {"note": "..."})
except FireConflictError as e:
    print(e.error_code, e.response)   # FLOW_RESUME_CONFLICT
Exception When
FireAuthError 401 — bad/missing/under-scoped token
FireBillingError 402/PRICING_TIER_UNAVAILABLE/INSUFFICIENT_BALANCE
FireValidationError 422 — bad request, an invalid flow spec (.response["details"]), or FLOW_QUOTA_EXCEEDED
FireConflictError 409 — e.g. resuming a run that isn't actually paused
FireNotFoundError 404 — unknown slug/run id, or a private flow you don't own
FireServerError 5xx
FireTimeoutError wait_for_flow/FlowRun.wait()'s own client-side poll timeout (not a Fire API error — subclasses the builtin TimeoutError)

Environments

FireClient(token="...", base_url="https://fire.test1.prosaga.net")  # default
FireClient(token="...", base_url="https://fire.prosaga.net")        # prod

base_url is the bare host — v1 and v2 are sibling path prefixes on Fire, not nested, and the SDK adds the right one per call.

Why typed objects (and why L1/L2/v2 don't share a base class)

The original design considered a class hierarchy mirroring Fire's model taxonomy end to end (a class per family/genus/species, chat() living on the leaf class). We didn't build that — the taxonomy is live, server- managed data (models get onboarded, deprecated, renamed continuously), so a class hierarchy would either drift stale or demand constant releases just to track it. Species (above) gets the discoverability without that coupling: it's generated data, not inherited behavior.

What did seem worth doing: typed results instead of bare dicts, so result.content beats result["content"] with real autocomplete and typo safety. But L1 (ChatResult/ImageResult), L2 (WorkflowResult), and v2 (FlowRun) are three separate root types, not one hierarchy — they mirror three genuinely different layers Fire enforces server-side (FirePipeline / WorkflowContract / FlowEngine), with different contracts and, for FlowRun, genuinely different behavior (stateful and pollable vs. one-shot). Forcing them under one base class would just be inheritance for organization's sake, not because they share behavior.

Not in scope yet

Streaming (/v1/chat/stream) isn't wrapped — this SDK is for LLM/script use where a blocking call is usually what you want. Open an issue (or just extend fire_sdk/client.py) if you need it.

Development

python -m unittest discover -s tests -v   # mocked HTTP, no network, no deps beyond stdlib

Regenerate fire_sdk/species.py (and the PHP/JS equivalents) after a model catalog change: python3 ../scripts/generate_species.py from this directory, or python3 scripts/generate_species.py from the repo root.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fire_sdk-1.0.1.tar.gz (22.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fire_sdk-1.0.1-py3-none-any.whl (16.9 kB view details)

Uploaded Python 3

File details

Details for the file fire_sdk-1.0.1.tar.gz.

File metadata

  • Download URL: fire_sdk-1.0.1.tar.gz
  • Upload date:
  • Size: 22.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.3

File hashes

Hashes for fire_sdk-1.0.1.tar.gz
Algorithm Hash digest
SHA256 e452b2055d42be830bb233e0a9faf186b7db0f71c4af414b8764b073aa8f015c
MD5 75c715162e0a5335f5bee9d3039cefaf
BLAKE2b-256 a20a9a7b25f9e6c726dc77d4d699e94deae1018c29654ad5ecb0781bd5ca5568

See more details on using hashes here.

File details

Details for the file fire_sdk-1.0.1-py3-none-any.whl.

File metadata

  • Download URL: fire_sdk-1.0.1-py3-none-any.whl
  • Upload date:
  • Size: 16.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.3

File hashes

Hashes for fire_sdk-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 9e27e0e1f787b603b8b0d5344c8a27d7fc87a2179e0532f58f436f919ecbb628
MD5 15f4096ba7dd81e6798a2fc47cba0ce6
BLAKE2b-256 e1896e0f14fb4849390ae8759111592bb0cd7c55f59afd8c4c427a28d200a2d5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page