Passing input arguments to your QiskitFunction

In this tutorial, we’ll make our Qiskit Function more flexible by allowing it to accept input arguments and return structured results. This way, you can reuse the same function for different configurations without modifying the code.

Install dependencies

Before we begin, make sure the required packages are installed in your notebook environment:

[ ]:
%pip install "qiskit==2.2.3" "qiskit-ibm-runtime==0.43.1" "qiskit-serverless==0.27.0"

Prep Work

Write the function with arguments

A QiskitFunction can receive arguments in the form of keyword pairs (for example: backend_name="ibm_nairobi", circuit=my_circuit). At runtime, the function retrieves these inputs by calling get_arguments(). The serverless framework deserializes them and returns a Python dictionary.

⚠ Only serializable arguments can be passed to the function. Qiskit uses QPY serialization for QuantumCircuit objects, which allows circuits to be safely transmitted and reconstructed. `get_arguments() <https://qiskit.github.io/qiskit-serverless/stubs/qiskit_serverless.serializers.get_arguments.html#qiskit_serverless.serializers.get_arguments>`__ must also be able to deserialize the object correctly.

Why pass the circuit instead of building it inside the function?

This approach makes the function reusable for different inputs and keeps its responsibility focused on execution steps: transpile → run → save results.

Now, create a new file called function_with_arguments.py inside source_files ./source_files/function_with_arguments.py. This function will:

  • Accept arguments (for example, a QuantumCircuit and a backend name).

  • Resolve a backend (fake or real) and transpile the circuit for it.

  • Run the Sampler primitive.

  • Save the results so they’re available via job.result() later.

"""function with arguments for jupyter notebook."""
import os
from qiskit import QuantumCircuit
from qiskit.providers.exceptions import QiskitBackendNotFoundError
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime.fake_provider import FakeProviderForBackendV2
from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit_serverless import get_arguments, save_result


# ----- parse inputs -----
# get all arguments passed to this function
print("[main] Parsing arguments...")
arguments = get_arguments()

# Extract inputs we care about
circuit = arguments.get("circuit")
backend_name = arguments.get("backend_name")
service = arguments.get("service")
print(f"[main] Inputs received (backend_name={backend_name})")

# Basic validation
if not isinstance(circuit, QuantumCircuit):
    raise ValueError("circuit must be QuantumCircuit.")
if not isinstance(backend_name, str) or len(backend_name) == 0:
    raise ValueError("backend_name must be a non-empty string.")

print(f"[main] Inputs received (backend_name={backend_name})")

# Choose a provider: fake provider for local testing, or a real servic
if "fake" in backend_name.lower():
    print(
        "[main] Using fake provider (auto-selected because backend_name contains 'fake')."
    )
    service = FakeProviderForBackendV2()

if isinstance(service, (FakeProviderForBackendV2, QiskitRuntimeService)):
    try:
        backend = service.backend(backend_name)
        print(f"[main] Backend resolved (name={backend.name})")
    except QiskitBackendNotFoundError as e:
        raise ValueError(f"Error retrieving backend {backend_name}: {e}") from e
else:
    # Fallback: build a Runtime service from environment variables
    print(
        "[main] No service provided and backend not fake; "
        "attempting to initialize QiskitRuntimeService from environment variables..."
    )

    try:
        service = QiskitRuntimeService(
            channel=os.environ.get("QISKIT_IBM_CHANNEL"),
            token=os.environ.get("QISKIT_IBM_TOKEN"),
            instance=os.environ.get("QISKIT_IBM_INSTANCE"),
            url=os.environ.get("QISKIT_IBM_URL"),
        )
        backend = service.backend(backend_name)
        print(
            f"[main] Runtime service initialized from env and backend "
            f"resolved (name={backend.name})"
        )
    except QiskitBackendNotFoundError as e:
        raise ValueError(f"The backend named {backend_name} couldn't be found.") from e
    except Exception as e:
        raise ValueError(
            f"`QiskitRuntimeService` couldn't be initialized with os environment variables: {e}."
        ) from e

# ----- transpile -----
# Match the run to the backend and transpile
print("[main] Building preset pass manager (optimization_level=3)...")
pm = generate_preset_pass_manager(backend=backend, optimization_level=3)
print("[main] Transpiling circuit...")
isa_circuit = pm.run(circuit)
print("[main] Transpilation complete")

# ----- execute -----
# Execute and collect counts
print(f"[main] Executing circuit on backend (name={backend.name})...")
sampler = Sampler(backend)
job_result = sampler.run([isa_circuit]).result()
print("[main] Sampler execution complete")

# Extract counts
quasi_dists = job_result[0].data.meas.get_counts()
print("[main] Extracted counts from result")

# ----- persist -------
# saving results of the execution
save_result({"quasi_dists": quasi_dists})
print("[main] Results saved.")

This function resolves the backend through a QiskitRuntimeService. For real IBM Quantum backends, the service can be provided explicitly as an argument (recommended for notebooks and local runs) or constructed dynamically from environment variables. These variables—such as QISKIT_IBM_CHANNEL, QISKIT_IBM_TOKEN, QISKIT_IBM_INSTANCE, and QISKIT_IBM_URL —are typically set by your DevOps or cloud team during deployment (for example, in Kubernetes manifests or Docker Compose). When present, the function uses them to authenticate and configure the runtime service without hardcoding credentials in the code. For more information visit Cloud infrastructure setup.

Deploying the function

Now that we have a function that accepts arguments, we need to upload it to the serverless gateway and run it with custom inputs. > ⚠ This provider is set up with default credentials to a test cluster intended to run on your machine. For information on setting up infrastructure on your local machine, check out the guide on local infrastructure setup.

[1]:
from qiskit_serverless import ServerlessClient, QiskitFunction
import os

client = ServerlessClient(
    token=os.environ.get("GATEWAY_TOKEN", "awesome_token"),
    instance=os.environ.get("GATEWAY_INSTANCE", "an_awesome_crn"),
    host=os.environ.get("GATEWAY_HOST", "http://localhost:8000"),
    # If you are using the kubernetes approach the URL must be http://localhost
)

function_with_args = QiskitFunction(
    title="function-with-arguments",
    entrypoint="function_with_arguments.py",
    working_dir="./source_files/",
)

client.upload(function_with_args)
[1]:
QiskitFunction(function-with-arguments)

Running the QiskitFunction

Testing environment

After deploying function-with-arguments, let’s test it. For quick, credential‑free testing we’ll use a simulator. In this example we choose FakeVigoV2 and pass its name as the backend_name. (The function detects “fake” in the name and constructs a fake provider internally.)

[2]:
from qiskit import QuantumCircuit
from qiskit_ibm_runtime.fake_provider import FakeVigoV2

backend_name = FakeVigoV2().name  # e.g., "fake_vigo"

circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
circuit.draw()
[2]:
        ┌───┐      ░ ┌─┐
   q_0: ┤ H ├──■───░─┤M├───
        └───┘┌─┴─┐ ░ └╥┘┌─┐
   q_1: ─────┤ X ├─░──╫─┤M├
             └───┘ ░  ║ └╥┘
meas: 2/══════════════╩══╩═
                      0  1 

Run the function on the simulator backend:

[3]:
my_function = client.get("function-with-arguments")

# For fake backends, passing `service` is not required.
job = my_function.run(circuit=circuit, backend_name=backend_name)
job

[3]:
<Job | b8c3d397-1428-476a-81c0-64e1cbc4719e>
[4]:
job.status()
[4]:
'QUEUED'

Retrieve the results from the client:

[5]:
job.result()   # Returns {'quasi_dists': {...}}
[5]:
{'quasi_dists': {'00': 523, '11': 501}}

Check the logs:

[6]:
print(job.logs())
2025-11-12 14:20:46,675 INFO job_manager.py:568 -- Runtime env is setting up.
[main] Parsing arguments...
[main] Inputs received (backend_name=fake_vigo)
[main] Inputs received (backend_name=fake_vigo)
[main] Using fake provider (auto-selected because backend_name contains 'fake').
[main] Backend resolved (name=fake_vigo)
[main] Building preset pass manager (optimization_level=3)...
[main] Transpiling circuit...
[main] Transpilation complete
[main] Executing circuit on backend (name=fake_vigo)...
[main] Sampler execution complete
[main] Extracted counts from result
[main] Results saved.

Running on a real backend (IBM Quantum Platform)

To run on real hardware (or managed cloud simulators), create a `QiskitRuntimeService <https://quantum.cloud.ibm.com/docs/en/guides/initialize-account>`__ instance and pass it to the function alongside a real backend_name. You can either use previously saved credentials or supply them directly (uncomment as needed). Then select a backend—for example, the least busy operational device—and pass its name.

[ ]:
from qiskit_ibm_runtime import QiskitRuntimeService

# If you have saved your account, this can be empty:
service = QiskitRuntimeService(
    # channel="ibm_quantum_platform",
    # token="API_KEY",
    # instance="CRN",
)

backend = service.least_busy(simulator=False, operational=True)
backend_name = backend.name  # use the string name expected by the function

job = my_function.run(circuit=circuit, backend_name=backend_name, service=service)
job

Retrieve results and logs:

[ ]:
print(f"Results: \n {job.result()}")
print(f"Logs: \n {job.logs()})