Release Notes

v0.30.0

Prelude

The main feature in this release is the major refactor in logs to differentiate between provider logs and user logs and optimize storage. The logs are now stored in COS while keeping backward compatibility.

New Features

  • The qiskit-serverless Python library added two new utilities, get_logger() and get_provider_logger(), available to Qiskit Function developers. Developers should use these functions within their source code to distinguish which logging information is relevant for users (get_logger()) or providers (get_provider_logger()) respectively. These functions create [PUBLIC] and [PRIVATE] prefixes under the hood to allow the service to filter the information appropriately. To keep intellectual property safe, only function users can access user logs, and only providers can access provider logs. For example:

    from qiskit_serverless import get_logger, get_provider_logger
    
    user_logger = get_logger()
    provider_logger = get_provider_logger()
    
    get_logger().info("User log")
    user_logger.info("User multiline\nlog")
    user_logger.warning("User log")
    user_logger.error("User log")
    
    get_provider_logger().info("Provider log")
    provider_logger.info("Provider multiline\nlog")
    provider_logger.warning("Provider log")
    provider_logger.error("Provider log")
    
  • The ServerlessClient and Job classes’ logs() method will from now on only return user logs. To access provider logs, we have added a new provider_logs() method. For example:

    runnable_function = serverless_client.function("my_function")
    job = runnable_function.run()
    
    print(job.logs())  # only users
    print(job.provider_logs())  # only providers
    
  • Uploaded files now should have a valid file type in order to be accepted by the platform.

    The accepted MIME types for uploaded files are:

    • application/x-tar (.tar)

    • application/gzip (.gz, .tgz)

    • application/json (.json)

    • application/octet-stream (generic binary; may include .bin, .dat, .exe)

    • application/zip (.zip)

    • text/plain (.txt, .log)

    • text/csv (.csv)

Upgrade Notes

  • Added a new version field to the Program Django model. This field is used to track the version of the program (function).

  • Added a new JobEvents table to our database to improve metrics and provide a more flexible foundation for future event-tracking features. Each time a job changes state, a new event is now recorded in the database.

    The new JobEvents table includes:
    • id: auto-generated UUID

    • job: foreign key referencing the jobs table

    • created: timestamp automatically set at event creation

    • event_type: string describing the event type (currently "Status change")

    • context: string indicating where the change originated (e.g., Scheduler, API)

    • data: JSON payload containing event‑specific details

  • Added several quality of life improvements in testing.

  • The Scheduler is modified to upload logs to COS when a job transitions from running to a terminal state. At that moment, the logs will be filtered based on the prefixes, and the information will be saved in the corresponding folder inside COS.

  • The Gateway endpoint /logs has been modified to only target user logs, and a new /provider_logs` has been added for provider logs. Both should return: 1. The COS log for finished jobs, if it exists. This was previously filtered in the Scheduler. 2. The Ray console logs if the job is running. This is filtered live. 3. As a fallback, logs from the database.

  • Four new exception classes have been added to the gateway API to better differentiate between different failure scenarios: JobNotFoundException, ProviderNotFoundException, FunctionNotFoundException and FileNotFoundException that inherit from an abstract NotFoundError base class.

  • Added a new gateway setting to limit the number of active jobs per user. By setting the environment variable LIMITS_ACTIVE_JOBS_PER_USER, you can restrict the total number of jobs a user can have across QUEUED, PENDING, and RUNNING states. The default limit is 50.

  • Requests to run programs will now return an HTTP 429 Too Many Requests response if a user attempts to start a new job while they are at their active job limit.

  • The Scheduler and API code have been refactored to improve their maintainability and scalability. Most changes are internal and do not affect the user experience, but some additions have been made, for example, the scheduler can now respond to SIGINT and SIGTERM signals, and the API now accepts a dynamic configuration system that allows changing application settings at runtime.

Deprecation Notes

  • The @distribute_qiskit_function decorator has been deprecated and will be removed in following releases. The decorator was designed for remote program execution, a functionality that is now provided through the different serverless clients through the upload method.

Bug Fixes

  • Fixed a bug in the function versioning workflow where the version set upon function creation couldn’t be uploaded to the platform, stored in the database, or retrieved upon function retrieval. After the fix, the function version can be tracked through the whole life cycle. For example:

    from qiskit_serverless import QiskitFunction
    
    my-func = QiskitFunction(
        title="my_func_title",
        entrypoint="my_func_entrypoint.py",
        working_dir="./source_files/",
        version="2.0.0",
    )
    
    # this print outputs 2.0.0
    print(my-func.version)
    
    serverless.upload(my-func)
    my-func-load = serverless.function("my_func_title")
    
    # this print now also outputs 2.0.0 (previously empty)
    print(my-func-load.version)
    
  • Fixed an issue that affected local testing when using custom functions packaged as Docker images. These custom functions are registered as “provider” functions, however, their data path was resolved to a “custom” function data path, resulting in a “File not found” error when trying to fetch function arguments. After the fix, the data paths are resolved correctly, and no error is raised:

    • For user functions: DATA_PATH = \data\username

    • For provider functions: DATA_PATH = \data\username\providername\imagename

v0.29.0

Upgrade Notes

  • Refactored storage service initialization to accept Program model instances instead of individual parameters. The FileStorage and ArgumentsStorage classes now accept a function parameter (Program model instance) rather than separate username, function_title, and provider_name parameters. This change improves code cohesion by reducing the number of parameters passed between components and ensures consistent access to program metadata across storage services.

  • Removed unused function_title and provider_name parameters from ProgramViewSet.run() method’s call to job_serializer.save() as these parameters are no longer used after the storage service refactoring.

  • Removed LocalClient and RayClient classes and their usages in the tests. For testing, one should use ServerlessClient with default values as in docs/deployment/example_custom_image_function.rst. In addition, updated tests and removed irrelevant comments.

  • The legacy IBM Quantum authentication channel (ibm_quantum) has been removed. Users should migrate to using the IBM Quantum Platform channel (ibm_quantum_platform) for authentication. The authentication logic has been simplified to support only ibm_cloud and ibm_quantum_platform channels. When no channel header is provided and a CRN is present, the system defaults to ibm_quantum_platform.

  • Qiskit Serverless now supports the latest version of the qiskit-ibm-runtime package:qiskit-ibm-runtime==0.45.

Bug Fixes

  • Fixed a bug in the json decoder that would crop the response details to the first character when gateway errors were raised. This utility now forwards the full error message to the raised QiskitServerlessException.

  • Fixed error handling when a function is not found. Instead of providing a bare 404 error, the QiskitServerlessException now contains a more meaningful error message:

    QiskitServerlessException:
    | Message: Http bad request.
    | Code: 404
    | Details: User program 'my-program' was not found or you do not have permission to view it.
    

    Fixes https://github.com/Qiskit/qiskit-serverless/issues/1773.

  • Fixed a bug in FunctionRepository.get_user_function() where functions with providers could be incorrectly returned when searching for user functions without providers. The method now correctly filters by provider=None to ensure only user-owned functions without providers are returned. This prevents incorrect storage path resolution when users have multiple functions with the same title.

v0.28.0

New Features

  • IBMServerlessClient now supports custom gateway host URLs through the host parameter. This allows for a more streamlined testing of the client connecting to non-production gateways.

  • Added a new ProgramHistory model and database table to track when entities perform actions to add or remove an instance from a program. These allow to audit access changes for both instances and trial_instances.

v0.27.1

Upgrade Notes

  • The scheduling logic that was managing non GPU and GPU jobs was improved. Now the scheduler manages both type of jobs independently so you can adjust separately where each of these jobs are run.

  • Qiskit Serverless now supports the latest version of the qiskit-ibm-runtime package:qiskit-ibm-runtime==0.43.

Bug Fixes

  • Fixed a bug in the scheduler that was preventing it from stopping jobs automatically after the timeout threshold specified with the environment variable PROGRAM_TIMEOUT.

v0.27.0

New Features

  • A new utility get_runtime_service() is now available to simplify the instantiation of a QiskitRuntimeService within a function. It automatically pulls credentials from environment variables, reducing boilerplate code and improving usability for function developers.

    Example usage:

    from qiskit_serverless import get_runtime_service
    service = get_runtime_service()
    backend = service.backend("ibm_fez")
    

    This is equivalent to:

    import os
    from qiskit_serverless import get_arguments, save_result
    from qiskit_ibm_runtime import QiskitRuntimeService
    service = QiskitRuntimeService(
      channel=os.environ["QISKIT_IBM_CHANNEL"],
      instance=os.environ["QISKIT_IBM_INSTANCE"],
      token=os.environ["QISKIT_IBM_TOKEN"],
    )
    backend = service.backend("ibm_fez")
    

    This function also enables automatic tracking of runtime job and session IDs created during function execution. It’s important to note that tracking will only be possible if the function runtime jobs are submitted through a service instantiated with this function.

    On top of the default credentials, function developers can customize the call to get_runtime_service() with custom input parameters, including token, instance, channel and url. For example:

    from qiskit_serverless import get_runtime_service
    service = get_runtime_service(channel="ibm_quantum_platform",
                                  token="staging_token",
                                  instance="staging_crn",
                                  url="staging_url")
    backend = service.backend("staging_backend")
    
  • When using get_runtime_service() inside a serverless function, the resulting job object now supports two new methods: job.runtime_jobs() and job.runtime_sessions(). These methods return lists of job/session IDs that can be used to fetch job objects from a QiskitRuntimeService or access IQP dashboards. job.runtime_jobs() accepts an optional runtime_session parameter that allows to filter the returned jobs by associated session id. For example:

    job = function.run(...)
    
    runtime_ids = job.runtime_jobs()
    # out = ["job_id_1", "job_id_2", "job_id_3"...]
    runtime_sessions =  job.runtime_sessions()
    # out = ["session_id_1", "session_id_2"]
    
    # a specific session id can be passed to the runtime_jobs() method to filter by session:
    
    session_id = job.runtime_sessions()[0]
    # out = "session_id_1"
    session_job_ids =  job.runtime_jobs(session_id)
    # in this example, only job ids 1 and 3 correspond to session_id_1:
    # out = ["job_id_1", "job_id_3"]
    

Upgrade Notes

  • Added a new AccessPolicy for User model to be able to allow or not the access to the service by the is_active parameter.

  • Enhanced job.cancel() behavior to use the newly introduced features. job.cancel() now attempts to instantiate a QiskitRuntimeService using the credentials from the ServerlessClient, allowing automatic canceling of associated runtime jobs. This works when the credentials used in the function match those in the client, which happens by default when using get_runtime_service() with no additional inputs.

    To support local testing or non-standard runtime URLs (e.g., staging environments), where the ServerlessClient don’t match those in the QiskitRuntimeService used to submit the jobs, job.cancel() accepts a service parameter:

    service = QiskitRuntimeService(
      channel="ibm_quantum_platform",
      token="MY_TOKEN",
      instance="MY_CRN",
      url="my.staging.url.com"
    )
    job.cancel(service)
    
  • The ServerlessRuntimeService class has been updated to support changes introduced in qiskit_ibm_runtime>=0.42, unlocking compatibility of the qiskit-serverless>=0.27.0 package with qiskit_ibm_runtime>=0.42. For older versions of qiskit-serverless, qiskit_ibm_runtime<0.42 will still be required.

  • The Scheduler will start to manage the size of the logs generated by the Qiskit Functions. The environment variable FUNCTIONS_LOGS_SIZE_LIMIT will be in charge of the maximum size that the system will allow to store.

  • Enhanced client.jobs() and function.jobs() behavior with new filters to improve the management of the different jobs. Now you can filter by status and by created_after to retrieve those specific jobs. For example:

    # Filtering by status it will retrieve all the jobs with that status
    client.jobs(status="SUCCEEDED")
    
    # The same it will apply after a specific date
    time_filter = datetime.now(timezone.utc)
    client.jobs(created_after=time_filter)
    
    # And all these new filters can be combined with the Qiskit Function filter
    # to be able to return running jobs from the specific Qiskit Function
    function.jobs(status="RUNNING")