From 378f0de439917bc76d41e216a98d082bc02447aa Mon Sep 17 00:00:00 2001 From: ehhuang Date: Sun, 6 Apr 2025 18:51:34 -0700 Subject: [PATCH 01/24] docs: llama4 getting started nb (#1878) # What does this PR do? ## Test Plan --- docs/getting_started_llama4.ipynb | 876 ++++++++++++++++++++++++++++++ docs/source/index.md | 5 + 2 files changed, 881 insertions(+) create mode 100644 docs/getting_started_llama4.ipynb diff --git a/docs/getting_started_llama4.ipynb b/docs/getting_started_llama4.ipynb new file mode 100644 index 000000000..d489b5d06 --- /dev/null +++ b/docs/getting_started_llama4.ipynb @@ -0,0 +1,876 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c1e7571c", + "metadata": { + "id": "c1e7571c" + }, + "source": [ + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/meta-llama/llama-stack/blob/main/docs/getting_started.ipynb)\n", + "\n", + "# Getting Started with Llama 4 in Llama Stack\n", + "\n", + "\"drawing\"\n", + "\n", + "[Llama Stack](https://github.com/meta-llama/llama-stack) defines and standardizes the set of core building blocks needed to bring generative AI applications to market. These building blocks are presented in the form of interoperable APIs with a broad set of Service Providers providing their implementations.\n", + "\n", + "Read more about the project here: https://llama-stack.readthedocs.io/en/latest/index.html\n", + "\n", + "In this guide, we will showcase how you can get started with using Llama 4 in Llama Stack.\n" + ] + }, + { + "cell_type": "markdown", + "id": "4CV1Q19BDMVw", + "metadata": { + "id": "4CV1Q19BDMVw" + }, + "source": [ + "## 1. Getting started with Llama Stack" + ] + }, + { + "cell_type": "markdown", + "id": "K4AvfUAJZOeS", + "metadata": { + "id": "K4AvfUAJZOeS" + }, + "source": [ + "### 1.1. Download Llama 4 Model\n", + "\n", + "In this showcase, we will use run Llama 4 locally. Note you need 8xH100 GPU-host to run these models." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8fb2e8b6", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install uv \n", + "\n", + "MODEL=\"Llama-4-Scout-17B-16E-Instruct\"\n", + "# get meta url from llama.com\n", + "!uv run --with llama-stackllama model download --source meta --model-id $MODEL --meta-url \n", + "\n", + "model_id = f\"meta-llama/{MODEL}\"" + ] + }, + { + "cell_type": "markdown", + "id": "oDUB7M_qe-Gs", + "metadata": { + "id": "oDUB7M_qe-Gs" + }, + "source": [ + "### 1.2. Setup and Running a Llama Stack server\n", + "\n", + "Llama Stack is architected as a collection of APIs that provide developers with the building blocks to build AI applications. \n", + "\n", + "Llama stack is typically available as a server with an endpoint that you can make calls to. Partners like Together and Fireworks offer their own Llama Stack compatible endpoints.\n", + "\n", + "In this showcase, we will start a Llama Stack server that is running locally.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "J2kGed0R5PSf", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "collapsed": true, + "id": "J2kGed0R5PSf", + "outputId": "2478ea60-8d35-48a1-b011-f233831740c5" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: uv in /opt/homebrew/Caskroom/miniconda/base/envs/l4/lib/python3.10/site-packages (0.6.12)\n", + "\u001b[2mUsing Python 3.10.16 environment at: /opt/homebrew/Caskroom/miniconda/base/envs/l4\u001b[0m\n", + "\u001b[2mAudited \u001b[1m1 package\u001b[0m \u001b[2min 83ms\u001b[0m\u001b[0m\n", + "Environment '/Users/erichuang/projects/internal-llama-stack/.venv' already exists, re-using it.\n", + "Virtual environment /Users/erichuang/projects/internal-llama-stack/.venv is already active\n", + "\u001b[2mUsing Python 3.11.11 environment at: /Users/erichuang/projects/internal-llama-stack/.venv\u001b[0m\n", + "\u001b[2mAudited \u001b[1m1 package\u001b[0m \u001b[2min 387ms\u001b[0m\u001b[0m\n", + "Installing pip dependencies\n", + "\u001b[2mUsing Python 3.11.11 environment at: /Users/erichuang/projects/internal-llama-stack/.venv\u001b[0m\n", + "\u001b[2K\u001b[2mResolved \u001b[1m123 packages\u001b[0m \u001b[2min 1.13s\u001b[0m\u001b[0m \u001b[0m\n", + "\u001b[2K\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6) \n", + "\u001b[2K\u001b[1A\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6)-----\u001b[0m\u001b[0m 0 B/9.53 KiB \u001b[1A\n", + "\u001b[2K\u001b[1A\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6)-\u001b[2m\u001b[0m\u001b[0m 9.53 KiB/9.53 KiB \u001b[1A\n", + "\u001b[2mshellingham\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 9.53 KiB/9.53 KiB\n", + "\u001b[2K\u001b[2A\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6)----\u001b[0m\u001b[0m 0 B/44.00 KiB \u001b[2A\n", + "\u001b[2mshellingham\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 9.53 KiB/9.53 KiB\n", + "\u001b[2K\u001b[2A\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6)----\u001b[0m\u001b[0m 14.88 KiB/44.00 KiB \u001b[2A\n", + "\u001b[2mshellingham\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 9.53 KiB/9.53 KiB\n", + "\u001b[2mtabulate \u001b[0m \u001b[32m\u001b[2m------------------------------\u001b[0m\u001b[0m 0 B/34.43 KiB\n", + "\u001b[2K\u001b[3A\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6)----\u001b[0m\u001b[0m 14.88 KiB/44.00 KiB \u001b[3A\n", + "\u001b[2mshellingham\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 9.53 KiB/9.53 KiB\n", + "\u001b[2mtabulate \u001b[0m \u001b[32m-------------\u001b[2m-----------------\u001b[0m\u001b[0m 14.83 KiB/34.43 KiB\n", + "\u001b[2K\u001b[3A\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6)----\u001b[0m\u001b[0m 14.88 KiB/44.00 KiB \u001b[3A\n", + "\u001b[2meval-type-backport\u001b[0m \u001b[32m\u001b[2m------------------------------\u001b[0m\u001b[0m 0 B/5.69 KiB\n", + "\u001b[2mshellingham\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 9.53 KiB/9.53 KiB\n", + "\u001b[2mtabulate \u001b[0m \u001b[32m-------------\u001b[2m-----------------\u001b[0m\u001b[0m 14.83 KiB/34.43 KiB\n", + "\u001b[2K\u001b[4A\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6)----\u001b[0m\u001b[0m 14.88 KiB/44.00 KiB \u001b[4A\n", + "\u001b[2meval-type-backport\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 5.69 KiB/5.69 KiB\n", + "\u001b[2mshellingham\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 9.53 KiB/9.53 KiB\n", + "\u001b[2mtabulate \u001b[0m \u001b[32m-------------\u001b[2m-----------------\u001b[0m\u001b[0m 14.83 KiB/34.43 KiB\n", + "\u001b[2K\u001b[4A\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6)----\u001b[0m\u001b[0m 14.88 KiB/44.00 KiB \u001b[4A\n", + "\u001b[2meval-type-backport\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 5.69 KiB/5.69 KiB\n", + "\u001b[2mshellingham\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 9.53 KiB/9.53 KiB\n", + "\u001b[2mtabulate \u001b[0m \u001b[32m-------------\u001b[2m-----------------\u001b[0m\u001b[0m 14.83 KiB/34.43 KiB\n", + "\u001b[2mtyper \u001b[0m \u001b[32m-----------\u001b[2m-------------------\u001b[0m\u001b[0m 14.88 KiB/44.00 KiB\n", + "\u001b[2K\u001b[5A\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6)----\u001b[0m\u001b[0m 0 B/85.81 KiB \u001b[5A\n", + "\u001b[2meval-type-backport\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 5.69 KiB/5.69 KiB\n", + "\u001b[2mshellingham\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 9.53 KiB/9.53 KiB\n", + "\u001b[2mtabulate \u001b[0m \u001b[32m-------------\u001b[2m-----------------\u001b[0m\u001b[0m 14.83 KiB/34.43 KiB\n", + "\u001b[2mtyper \u001b[0m \u001b[32m-----------\u001b[2m-------------------\u001b[0m\u001b[0m 14.88 KiB/44.00 KiB\n", + "\u001b[2K\u001b[5A\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6)----\u001b[0m\u001b[0m 16.00 KiB/85.81 KiB \u001b[5A\n", + "\u001b[2meval-type-backport\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 5.69 KiB/5.69 KiB\n", + "\u001b[2mshellingham\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 9.53 KiB/9.53 KiB\n", + "\u001b[2mtabulate \u001b[0m \u001b[32m-------------\u001b[2m-----------------\u001b[0m\u001b[0m 14.83 KiB/34.43 KiB\n", + "\u001b[2mtyper \u001b[0m \u001b[32m-----------\u001b[2m-------------------\u001b[0m\u001b[0m 14.88 KiB/44.00 KiB\n", + "\u001b[2mtogether \u001b[0m \u001b[32m------\u001b[2m------------------------\u001b[0m\u001b[0m 16.00 KiB/85.81 KiB\n", + "\u001b[2K\u001b[6A\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6)----\u001b[0m\u001b[0m 0 B/3.08 MiB \u001b[6A\n", + "\u001b[2meval-type-backport\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 5.69 KiB/5.69 KiB\n", + "\u001b[2mshellingham\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 9.53 KiB/9.53 KiB\n", + "\u001b[2mtabulate \u001b[0m \u001b[32m-------------\u001b[2m-----------------\u001b[0m\u001b[0m 14.83 KiB/34.43 KiB\n", + "\u001b[2mtyper \u001b[0m \u001b[32m-----------\u001b[2m-------------------\u001b[0m\u001b[0m 14.88 KiB/44.00 KiB\n", + "\u001b[2mtogether \u001b[0m \u001b[32m------\u001b[2m------------------------\u001b[0m\u001b[0m 16.00 KiB/85.81 KiB\n", + "\u001b[2K\u001b[6A\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6)----\u001b[0m\u001b[0m 14.91 KiB/3.08 MiB \u001b[6A\n", + "\u001b[2meval-type-backport\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 5.69 KiB/5.69 KiB\n", + "\u001b[2mshellingham\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 9.53 KiB/9.53 KiB\n", + "\u001b[2mtabulate \u001b[0m \u001b[32m---------------------------\u001b[2m---\u001b[0m\u001b[0m 30.83 KiB/34.43 KiB\n", + "\u001b[2mtyper \u001b[0m \u001b[32m-----------\u001b[2m-------------------\u001b[0m\u001b[0m 14.88 KiB/44.00 KiB\n", + "\u001b[2mtogether \u001b[0m \u001b[32m------\u001b[2m------------------------\u001b[0m\u001b[0m 16.00 KiB/85.81 KiB\n", + "\u001b[2K\u001b[6A\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6)----\u001b[0m\u001b[0m 14.91 KiB/3.08 MiB \u001b[6A\n", + "\u001b[2meval-type-backport\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 5.69 KiB/5.69 KiB\n", + "\u001b[2mshellingham\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 9.53 KiB/9.53 KiB\n", + "\u001b[2mtabulate \u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 34.43 KiB/34.43 KiB\n", + "\u001b[2mtyper \u001b[0m \u001b[32m-----------\u001b[2m-------------------\u001b[0m\u001b[0m 14.88 KiB/44.00 KiB\n", + "\u001b[2mtogether \u001b[0m \u001b[32m------\u001b[2m------------------------\u001b[0m\u001b[0m 16.00 KiB/85.81 KiB\n", + "\u001b[2K\u001b[6A\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6)----\u001b[0m\u001b[0m 14.91 KiB/3.08 MiB \u001b[6A\n", + "\u001b[2mshellingham\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 9.53 KiB/9.53 KiB\n", + "\u001b[2mtabulate \u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 34.43 KiB/34.43 KiB\n", + "\u001b[2mtyper \u001b[0m \u001b[32m-----------\u001b[2m-------------------\u001b[0m\u001b[0m 14.88 KiB/44.00 KiB\n", + "\u001b[2mtogether \u001b[0m \u001b[32m------\u001b[2m------------------------\u001b[0m\u001b[0m 16.00 KiB/85.81 KiB\n", + "\u001b[2K\u001b[5A\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6)----\u001b[0m\u001b[0m 14.91 KiB/3.08 MiB \u001b[5A\n", + "\u001b[2mshellingham\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 9.53 KiB/9.53 KiB\n", + "\u001b[2mtabulate \u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 34.43 KiB/34.43 KiB\n", + "\u001b[2mtyper \u001b[0m \u001b[32m-----------\u001b[2m-------------------\u001b[0m\u001b[0m 14.88 KiB/44.00 KiB\n", + "\u001b[2mtogether \u001b[0m \u001b[32m------\u001b[2m------------------------\u001b[0m\u001b[0m 16.00 KiB/85.81 KiB\n", + "\u001b[2K\u001b[5A\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6)----\u001b[0m\u001b[0m 30.91 KiB/3.08 MiB \u001b[5A\n", + "\u001b[2mshellingham\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 9.53 KiB/9.53 KiB\n", + "\u001b[2mtyper \u001b[0m \u001b[32m-----------\u001b[2m-------------------\u001b[0m\u001b[0m 14.88 KiB/44.00 KiB\n", + "\u001b[2mtogether \u001b[0m \u001b[32m------\u001b[2m------------------------\u001b[0m\u001b[0m 16.00 KiB/85.81 KiB\n", + "\u001b[2K\u001b[4A\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6)----\u001b[0m\u001b[0m 30.91 KiB/3.08 MiB \u001b[4A\n", + "\u001b[2mshellingham\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 9.53 KiB/9.53 KiB\n", + "\u001b[2mtyper \u001b[0m \u001b[32m-----------\u001b[2m-------------------\u001b[0m\u001b[0m 14.88 KiB/44.00 KiB\n", + "\u001b[2mtogether \u001b[0m \u001b[32m------\u001b[2m------------------------\u001b[0m\u001b[0m 16.00 KiB/85.81 KiB\n", + "\u001b[2K\u001b[4A\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6)----\u001b[0m\u001b[0m 46.91 KiB/3.08 MiB \u001b[4A\n", + "\u001b[2mshellingham\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 9.53 KiB/9.53 KiB\n", + "\u001b[2mtyper \u001b[0m \u001b[32m-----------\u001b[2m-------------------\u001b[0m\u001b[0m 14.88 KiB/44.00 KiB\n", + "\u001b[2mtogether \u001b[0m \u001b[32m------\u001b[2m------------------------\u001b[0m\u001b[0m 16.00 KiB/85.81 KiB\n", + "\u001b[2K\u001b[4A\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6)----\u001b[0m\u001b[0m 62.91 KiB/3.08 MiB \u001b[4A\n", + "\u001b[2mshellingham\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 9.53 KiB/9.53 KiB\n", + "\u001b[2mtyper \u001b[0m \u001b[32m-----------\u001b[2m-------------------\u001b[0m\u001b[0m 14.88 KiB/44.00 KiB\n", + "\u001b[2mtogether \u001b[0m \u001b[32m------\u001b[2m------------------------\u001b[0m\u001b[0m 16.00 KiB/85.81 KiB\n", + "\u001b[2K\u001b[4A\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6)----\u001b[0m\u001b[0m 78.91 KiB/3.08 MiB \u001b[4A\n", + "\u001b[2mshellingham\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 9.53 KiB/9.53 KiB\n", + "\u001b[2mtyper \u001b[0m \u001b[32m-----------\u001b[2m-------------------\u001b[0m\u001b[0m 14.88 KiB/44.00 KiB\n", + "\u001b[2mtogether \u001b[0m \u001b[32m------\u001b[2m------------------------\u001b[0m\u001b[0m 16.00 KiB/85.81 KiB\n", + "\u001b[2K\u001b[4A\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6)----\u001b[0m\u001b[0m 94.91 KiB/3.08 MiB \u001b[4A\n", + "\u001b[2mshellingham\u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 9.53 KiB/9.53 KiB\n", + "\u001b[2mtyper \u001b[0m \u001b[32m-----------\u001b[2m-------------------\u001b[0m\u001b[0m 14.88 KiB/44.00 KiB\n", + "\u001b[2mtogether \u001b[0m \u001b[32m------------\u001b[2m------------------\u001b[0m\u001b[0m 32.00 KiB/85.81 KiB\n", + "\u001b[2K\u001b[4A\u001b[37m⠙\u001b[0m \u001b[2mPreparing packages...\u001b[0m (0/6)----\u001b[0m\u001b[0m 2.62 MiB/3.08 MiB \u001b[4A\n", + "\u001b[2mtyper \u001b[0m \u001b[32m----------------------\u001b[2m--------\u001b[0m\u001b[0m 30.88 KiB/44.00 KiB\n", + "\u001b[2mtogether \u001b[0m \u001b[32m------------\u001b[2m------------------\u001b[0m\u001b[0m 32.00 KiB/85.81 KiB\n", + "\u001b[2K\u001b[3A\u001b[37m⠹\u001b[0m \u001b[2mPreparing packages...\u001b[0m (3/6)----\u001b[0m\u001b[0m 2.62 MiB/3.08 MiB \u001b[3A\n", + "\u001b[2mtyper \u001b[0m \u001b[32m------------------------------\u001b[2m\u001b[0m\u001b[0m 44.00 KiB/44.00 KiB\n", + "\u001b[2mtogether \u001b[0m \u001b[32m------------\u001b[2m------------------\u001b[0m\u001b[0m 32.00 KiB/85.81 KiB\n", + "\u001b[2K\u001b[3A\u001b[37m⠹\u001b[0m \u001b[2mPreparing packages...\u001b[0m (3/6)----\u001b[0m\u001b[0m 2.62 MiB/3.08 MiB \u001b[3A\n", + "\u001b[2mtogether \u001b[0m \u001b[32m------------\u001b[2m------------------\u001b[0m\u001b[0m 32.00 KiB/85.81 KiB\n", + "\u001b[2K\u001b[2A\u001b[37m⠹\u001b[0m \u001b[2mPreparing packages...\u001b[0m (3/6)2m--\u001b[0m\u001b[0m 2.80 MiB/3.08 MiB \u001b[2A\n", + "\u001b[2mtogether \u001b[0m \u001b[32m-----------------\u001b[2m-------------\u001b[0m\u001b[0m 48.00 KiB/85.81 KiB\n", + "\u001b[2K\u001b[2A\u001b[37m⠹\u001b[0m \u001b[2mPreparing packages...\u001b[0m (3/6)2m--\u001b[0m\u001b[0m 2.81 MiB/3.08 MiB \u001b[2A\n", + "\u001b[2K\u001b[1A\u001b[37m⠹\u001b[0m \u001b[2mPreparing packages...\u001b[0m (3/6)----\u001b[0m\u001b[0m 48.00 KiB/85.81 KiB \u001b[1A\n", + "\u001b[2K\u001b[1A\u001b[37m⠹\u001b[0m \u001b[2mPreparing packages...\u001b[0m (3/6)2m--\u001b[0m\u001b[0m 80.00 KiB/85.81 KiB \u001b[1A\n", + "\u001b[2K\u001b[2mPrepared \u001b[1m6 packages\u001b[0m \u001b[2min 365ms\u001b[0m\u001b[0m \u001b[1A\n", + "\u001b[2K\u001b[2mInstalled \u001b[1m6 packages\u001b[0m \u001b[2min 50ms\u001b[0m\u001b[0m \u001b[0m\n", + " \u001b[32m+\u001b[39m \u001b[1meval-type-backport\u001b[0m\u001b[2m==0.2.2\u001b[0m\n", + " \u001b[32m+\u001b[39m \u001b[1mfaiss-cpu\u001b[0m\u001b[2m==1.10.0\u001b[0m\n", + " \u001b[32m+\u001b[39m \u001b[1mshellingham\u001b[0m\u001b[2m==1.5.4\u001b[0m\n", + " \u001b[32m+\u001b[39m \u001b[1mtabulate\u001b[0m\u001b[2m==0.9.0\u001b[0m\n", + " \u001b[32m+\u001b[39m \u001b[1mtogether\u001b[0m\u001b[2m==1.5.5\u001b[0m\n", + " \u001b[32m+\u001b[39m \u001b[1mtyper\u001b[0m\u001b[2m==0.15.2\u001b[0m\n", + "torch torchvision --index-url https://download.pytorch.org/whl/cpu\n", + "\u001b[2mUsing Python 3.11.11 environment at: /Users/erichuang/projects/internal-llama-stack/.venv\u001b[0m\n", + "\u001b[2mAudited \u001b[1m2 packages\u001b[0m \u001b[2min 32ms\u001b[0m\u001b[0m\n", + "sentence-transformers --no-deps\n", + "\u001b[2mUsing Python 3.11.11 environment at: /Users/erichuang/projects/internal-llama-stack/.venv\u001b[0m\n", + "\u001b[2mAudited \u001b[1m1 package\u001b[0m \u001b[2min 63ms\u001b[0m\u001b[0m\n", + "\u001b[32mBuild Successful!\u001b[0m\n" + ] + } + ], + "source": [ + "import os \n", + "import subprocess\n", + "import time\n", + "\n", + "!uv pip install requests\n", + "\n", + "if \"UV_SYSTEM_PYTHON\" in os.environ:\n", + " del os.environ[\"UV_SYSTEM_PYTHON\"]\n", + "\n", + "# this command installs all the dependencies needed for the llama stack server \n", + "!uv run --with llama-stack llama stack build --template meta-reference-gpu --image-type venv \n", + "\n", + "def run_llama_stack_server_background():\n", + " log_file = open(\"llama_stack_server.log\", \"w\")\n", + " process = subprocess.Popen(\n", + " f\"uv run --with llama-stack llama stack run meta-reference-gpu --image-type venv --env INFERENCE_MODEL={model_id}\",\n", + " shell=True,\n", + " stdout=log_file,\n", + " stderr=log_file,\n", + " text=True\n", + " )\n", + " \n", + " print(f\"Starting Llama Stack server with PID: {process.pid}\")\n", + " return process\n", + "\n", + "def wait_for_server_to_start():\n", + " import requests\n", + " from requests.exceptions import ConnectionError\n", + " import time\n", + " \n", + " url = \"http://0.0.0.0:8321/v1/health\"\n", + " max_retries = 30\n", + " retry_interval = 1\n", + " \n", + " print(\"Waiting for server to start\", end=\"\")\n", + " for _ in range(max_retries):\n", + " try:\n", + " response = requests.get(url)\n", + " if response.status_code == 200:\n", + " print(\"\\nServer is ready!\")\n", + " return True\n", + " except ConnectionError:\n", + " print(\".\", end=\"\", flush=True)\n", + " time.sleep(retry_interval)\n", + " \n", + " print(\"\\nServer failed to start after\", max_retries * retry_interval, \"seconds\")\n", + " return False\n", + "\n", + "\n", + "# use this helper if needed to kill the server \n", + "def kill_llama_stack_server():\n", + " # Kill any existing llama stack server processes\n", + " os.system(\"ps aux | grep -v grep | grep llama_stack.distribution.server.server | awk '{print $2}' | xargs kill -9\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "c40e9efd", + "metadata": {}, + "source": [ + "### 1.3 Starting the Llama Stack Server" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f779283d", + "metadata": {}, + "outputs": [], + "source": [ + "server_process = run_llama_stack_server_background()\n", + "assert wait_for_server_to_start()" + ] + }, + { + "cell_type": "markdown", + "id": "90eb721b", + "metadata": {}, + "source": [ + "### 1.4 Install and Configure the Client\n", + "\n", + "Now that we have our Llama Stack server running locally, we need to install the client package to interact with it. The `llama-stack-client` provides a simple Python interface to access all the functionality of Llama Stack, including:\n", + "\n", + "- Chat Completions ( text and multimodal )\n", + "- Safety Shields \n", + "- Agent capabilities with tools like web search, RAG with Telemetry\n", + "- Evaluation and scoring frameworks\n", + "\n", + "The client handles all the API communication with our local server, making it easy to integrate Llama Stack's capabilities into your applications.\n", + "\n", + "In the next cells, we'll:\n", + "\n", + "1. Install the client package\n", + "2. Initialize the client to connect to our local server\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2e68e32a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[2mUsing Python 3.10.16 environment at: /opt/homebrew/Caskroom/miniconda/base/envs/stack\u001b[0m\n", + "\u001b[2K\u001b[2mResolved \u001b[1m31 packages\u001b[0m \u001b[2min 284ms\u001b[0m\u001b[0m \u001b[0m\n", + "\u001b[2mAudited \u001b[1m31 packages\u001b[0m \u001b[2min 0.04ms\u001b[0m\u001b[0m\n" + ] + } + ], + "source": [ + "!pip install -U llama-stack-client" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "E1UFuJC570Tk", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000, + "referenced_widgets": [ + "75307e3dee604d30aa44713e6e293e64", + "5ce87402a79342af995df41ac3940d55", + "fbbcc19886cc43b38424fbb184162c61", + "29212208db6b432eb4f708cd64258954", + "50dd8994a4cf486ebbec5ffd4322992a", + "f9b768c703494dd198f2978aff4892e8", + "1231b9e4cab34c33a38bee63543f1e75", + "754deb3970604d48a522bc9f021ad945", + "f6ecca7a1a8340fbbe056235a2714fc3", + "ef4f63fe9d8f4683a9d20becb6e4e2cb", + "7508f10c13634e7aa682cfb29c48d9e7", + "26f1430ca7cb4ad5b1b8df1ffdbd32a9", + "7cd2d9c9ea7b4d70902ffaff33033078", + "101288236cff40b8bb9dbad80dbbc7ee", + "d5c9977838a249eeab6ef628279b8155", + "d032d1e7b4b54ba28ac83c1a12b23876", + "321fce57c158432abeae496ae8a947aa", + "3ebe00201bdb4e119e3b74f684a58345", + "0f8bab6b8ed04774b386fe952aae66f1", + "cfcb6e456c354d99be91f161552f3376", + "61bd0d490c0e4c04a331cf9ce6b7d38f", + "7d8653fca29f4df3a7487733ff9db60b", + "943f8fcb66614353a51f32f8344b6122", + "0e695245b97c4bbc85e349fda3dc07b9", + "bb0d168c41f540b8ae42239d3938483a", + "87700a80125348f28c4f249bdf8b0a8d", + "8902c3622da540e496ed5b1524bd01ca", + "90432ec1c24b4607a935c94e130cd68d", + "464147b149824f20afc727751a702fc7", + "67e37a088be64a2ba786ca923b1017dd", + "98786f52ef5345b0b9164b9c1f2b8e18", + "0e1b9910a77d4b7fa69cb8926e6547d7", + "0b276315be4345be83da1e03905c8495", + "e11f8c3891284e07bd2572257afd5e1b", + "ee18d96394994d01b49d5b03b3d9a019", + "844b06df5749441fab6f61656ce581a9", + "e1c6b9a20e074f17aeba976b24e80c65", + "c690da8daa1e4f9ea73bcacdd92e8a6d", + "d0b161ae25c441e8b3caf7a3d88c1b05", + "47cf4b6b835d43388576a2abf4cc54f8", + "03bbebd659e64b5d9c29a73570c34854", + "b68e5097d2504d2cbd7e19aa1aac3a04", + "22a665deff88477b9372c0350c4c572b", + "5e535ed2b83e496ab57b1c80b615ab0c", + "d9de065c7f81443e98ddf066c7b5bd54", + "1e836106837c4ac7a11b36e700c46b64", + "55591e8179084fcfa3a61c8bd8d09dcb", + "de1ef93c41364eda9b4b111231057348", + "23b0b2f4f82c4a21846e91d7cea91da5", + "9e4d0fbb51284a7487c495c7b95a293d", + "b0f8cf1f79e04b5fb47a810f2c81bd7e", + "0c359bc4c94c46acbc9094354a15c33d", + "59d0b59b6c2248508d0601ff13878d33", + "891cb726d45c4fef8f2c74a56df5532b", + "fa39189070334939aea5fa4a7de5ec8b", + "f0e107dd6d54483aa367da0e337a97cd", + "861a00796f55470e85d94733eeee9a5f", + "5459633eb6e94ec391d13fcf67425726", + "b7b7467ece304ffbbd352b9b96a03aad", + "9dece059f1204e29b106fca9e191ddb3", + "e2e49c25d6fc4592b317e94cfabc2e5e", + "76d37a48a73946bab2821f097cf2605f", + "8e81ae00681347cb906b392c3656a64a", + "74bedc38b7da4e8a83b0c892d7aa59b5", + "d1e67c28b4664e8098dce8f5e80b8779", + "abe6cf39b784436993fcbe92221c31a3", + "d021a18ab70b4c7e8aec43932a124c36", + "72e7c092fb054b7ea0dcd2782b5d8a7d", + "8b1ea80221174fae943d5c9f997dfb57", + "f8073d625f80415dbf712cee434f6e3a", + "5f6014ba13fa4a659b9eb1b5f83599a7", + "327ff8f5292d47afbfebd3beea187739", + "988cac4341b646079fc73719f3f88ad7", + "900a4dac08f540dfb35c29f63236a12c", + "1e6009b9b0684b8fbaa379ea96f111ee", + "541b9b4e74614e2cb855bb90f03df538", + "ff256b2275f740ed82bca4f43b4d6fd2", + "3703041a499c426bb427ee008c81cde5", + "4b22bbacb995425fb32a2368f3685a92", + "49a66eeb9ef74de5ab8904fd90eb7558", + "08f9d125018b41c582a0fa1e234315f9", + "736c770230644894b85dbc34bd8f1d52", + "b67cbbf32f844a19b219be612d5038c9", + "774b513d64524ac7823a2cf13efa8d41", + "1e56da93bcf64ff490416d2b66cd3dc0", + "b7e35038ce344110b785753b655130f5", + "5472af91737446f4a4a2d92a3f684a45", + "9fb4368802da4a5a8101ba200d98403a", + "2e713bcc372e48b2a006558db4d1df68", + "1a277abd5ea44253bc6894bef258b52b", + "b3eedd82e7da4ce8b3ded70e49a2afd0", + "6f5c18cb8002471f8b3764effee37324", + "3bebac362b344e8d9103c5011613f1ea", + "670905a55b19458da69f83c8bcd511d1", + "ff54451a48394faaaa9d8cdb690d0718", + "36b5bc19b2d0407f8ab28ff0da2ce12d", + "879e48d9a9e04183903d94ffe98313d2", + "abce503d70594c2ca9afdc47847c125b", + "028e291ee53947bbbbc4bfb68c695f5f", + "a530662719374c95a9bef12e59e28c85", + "bffc0f4b12f141398535990709fd4f2c", + "04804c74e1dd43449d5f758cf5d0ba5e", + "95a506c3007c4525b01ee4e1600d671b", + "a0d6b0caeb2340fe96c8f5569e3d3ae4", + "30798f87a8b848d783fdacd71af5dc04", + "07ce54c75e76488ba4019a20b3707061", + "f023175de68445f98a6b01bb40ccdc6d", + "7389b79a0ff44cd68c7866995d728023", + "8e2b70ffe4eb4974bd6393fcc1292267", + "13eee164dc534424acb9dc9ee37a9465", + "722a7fe16af3422585a20c651345cfa4", + "f5596c1c9c4d42f3bc171961f9582eff", + "85d66e615b5742e78657b1e60c75fc72", + "731c02dc5dd446c3b22765575148e256", + "254ce460ce244c99a5afe39d5d51f6b7", + "4cf1dc345ace4da59f978f661487f975", + "8f30fca71bf24e5ca26e17c2321f893c", + "dd85d37dd1d14c7ea4592f8e11b2d2c8", + "3cb06377e4454f009d6b2aa7aa6ff0a9", + "4502477db4d948e693012364c2dcb370", + "52fe404ec9c14db2a7279b4c154eef3d" + ] + }, + "collapsed": true, + "id": "E1UFuJC570Tk", + "outputId": "aebb69d4-c167-4de5-eb8a-dd19dd538f63" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Not in Google Colab environment\n" + ] + } + ], + "source": [ + "from llama_stack_client import LlamaStackClient\n", + "\n", + "client = LlamaStackClient(\n", + " base_url=\"http://0.0.0.0:8321\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "635a7a6f", + "metadata": {}, + "source": [ + "Now that we have completed the setup and configuration, let's start exploring the capabilities of Llama 4!\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "0fc75d73", + "metadata": {}, + "source": [ + "## 2. Running Llama 4" + ] + }, + { + "cell_type": "markdown", + "id": "7dacaa2d-94e9-42e9-82a0-73522dfc7010", + "metadata": { + "id": "7dacaa2d-94e9-42e9-82a0-73522dfc7010" + }, + "source": [ + "### 2.1 Check available models\n", + "\n", + "All the models available are programmatically accessible via the client." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ruO9jQna_t_S", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "collapsed": true, + "id": "ruO9jQna_t_S", + "outputId": "ab1722a7-62ab-43bb-9cab-4e45bf62068a" + }, + "outputs": [], + "source": [ + "from rich.pretty import pprint\n", + "\n", + "print(\"Available models:\")\n", + "for m in client.models.list():\n", + " print(f\"- {m.identifier}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "86366383", + "metadata": { + "id": "86366383" + }, + "source": [ + "### 2.2 Run a simple chat completion with one of the models\n", + "\n", + "We will test the client by doing a simple chat completion." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "77c29dba", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "77c29dba", + "outputId": "4857974f-4c70-4bc4-f90a-6ae49dc9c41e" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Here is a two-sentence poem about a llama:\n", + "\n", + "With soft fur and gentle eyes, the llama roams with gentle surprise, a peaceful presence in the Andean skies. Its calm demeanor and soft humming song bring serenity to all who belong.\n" + ] + } + ], + "source": [ + "response = client.inference.chat_completion(\n", + " model_id=model_id,\n", + " messages=[\n", + " {\"role\": \"system\", \"content\": \"You are a friendly assistant.\"},\n", + " {\"role\": \"user\", \"content\": \"Write a two-sentence poem about llama.\"},\n", + " ],\n", + ")\n", + "\n", + "print(response.completion_message.content)\n" + ] + }, + { + "cell_type": "markdown", + "id": "7737cd41", + "metadata": {}, + "source": [ + "### 2.3 Running multimodal inference" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "e7b1baa7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " % Total % Received % Xferd Average Speed Time Time Time Current\n", + " Dload Upload Total Spent Left Speed\n", + "100 275k 100 275k 0 0 847k 0 --:--:-- --:--:-- --:--:-- 845k--:--:-- --:--:-- 0\n" + ] + }, + { + "data": { + "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/4QmWaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2MiI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOmlwdGNFeHQ9Imh0dHA6Ly9pcHRjLm9yZy9zdGQvSXB0YzR4bXBFeHQvMjAwOC0wMi0yOS8iIGlwdGNFeHQ6RGlnaXRhbFNvdXJjZVR5cGU9InRyYWluZWRBbGdvcml0aG1pY01lZGlhIi8+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPD94cGFja2V0IGVuZD0idyI/Pv/bAEMAAgEBAQEBAgEBAQICAgICBAMCAgICBQQEAwQGBQYGBgUGBgYHCQgGBwkHBgYICwgJCgoKCgoGCAsMCwoMCQoKCv/bAEMBAgICAgICBQMDBQoHBgcKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCv/AABEIAwADAAMBEQACEQEDEQH/xAAfAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgv/xAC1EAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYT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", + "text/plain": [ + "" + ] + }, + "execution_count": 15, + "metadata": { + "image/jpeg": { + "height": 256, + "width": 256 + } + }, + "output_type": "execute_result" + } + ], + "source": [ + "!curl -O https://raw.githubusercontent.com/meta-llama/llama-models/refs/heads/main/Llama_Repo.jpeg\n", + "\n", + "from IPython.display import Image\n", + "Image(\"Llama_Repo.jpeg\", width=256, height=256)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "e1450ecc", + "metadata": {}, + "outputs": [], + "source": [ + "import base64\n", + "def encode_image(image_path):\n", + " with open(image_path, \"rb\") as image_file:\n", + " base64_string = base64.b64encode(image_file.read()).decode(\"utf-8\")\n", + " base64_url = f\"data:image/png;base64,{base64_string}\"\n", + " return base64_url" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "d7914894", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The image features three llamas, each with a distinct color. The llama on the left is white, the middle one is purple, and the one on the right is also white but wears a blue party hat.\n", + "\n", + "To determine the number of different colors present, we can count the unique hues:\n", + "\n", + "1. White (two llamas)\n", + "2. Purple (one llama)\n", + "3. Blue (party hat)\n", + "\n", + "Therefore, there are 3 different colors visible in the image: white, purple, and blue.\n" + ] + } + ], + "source": [ + "response = client.inference.chat_completion(\n", + " messages=[\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": [\n", + " {\n", + " \"type\": \"image\",\n", + " \"image\": {\n", + " \"url\": {\n", + " \"uri\": encode_image(\"Llama_Repo.jpeg\")\n", + " }\n", + " }\n", + " },\n", + " {\n", + " \"type\": \"text\",\n", + " \"text\": \"How many different colors are those llamas? What are those colors?\",\n", + " }\n", + " ]\n", + " }\n", + " ],\n", + " model_id=model_id,\n", + " stream=False,\n", + ")\n", + "\n", + "print(response.completion_message.content)" + ] + }, + { + "cell_type": "markdown", + "id": "8cf0d555", + "metadata": { + "id": "8cf0d555" + }, + "source": [ + "### 2.4 Have a conversation\n", + "\n", + "Maintaining a conversation history allows the model to retain context from previous interactions. Use a list to accumulate messages, enabling continuity throughout the chat session." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "3fdf9df6", + "metadata": { + "id": "3fdf9df6" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[36m> Response: The most famous Prime Minister of England during World War 2 was Winston Churchill. He served as the Prime Minister of the United Kingdom from 1940 to 1945, and again from 1951 to 1955. Churchill is widely regarded as one of the greatest wartime leaders in history, known for his leadership, oratory skills, and unwavering resolve during the war.\n", + "\n", + "Churchill played a crucial role in rallying the British people during the war, and his speeches, such as the \"We shall fight on the beaches\" and \"Their finest hour\" speeches, are still remembered and celebrated today. He worked closely with other Allied leaders, including US President Franklin D. Roosevelt and Soviet leader Joseph Stalin, to coordinate the war effort and ultimately secure the defeat of Nazi Germany.\n", + "\n", + "Churchill's leadership and legacy have endured long after the war, and he remains one of the most iconic and influential figures in British history.\u001b[0m\n", + "\u001b[36m> Response: Winston Churchill was known for his many memorable quotes, but one of his most famous is:\n", + "\n", + "**\"We shall fight on the beaches, we shall fight on the landing grounds, we shall fight in the fields and in the streets, we shall fight in the hills; we shall never surrender.\"**\n", + "\n", + "This quote is from his speech to the House of Commons on June 4, 1940, during the early stages of World War II, when Nazi Germany was threatening to invade Britain. The speech is known as the \"We Shall Fight on the Beaches\" speech, and it's considered one of the greatest speeches of the 20th century.\n", + "\n", + "However, if I had to pick a single, even more concise quote, it would be:\n", + "\n", + "**\"Blood, toil, tears, and sweat.\"**\n", + "\n", + "This was the opening phrase of his first speech as Prime Minister to the House of Commons on May 13, 1940, in which he said:\n", + "\n", + "\"I say to the House as I said to those who have joined this Government, I have nothing to offer but blood, toil, tears, and sweat. We have before us an ordeal of the most grievous kind.\"\n", + "\n", + "This quote has become synonymous with Churchill's leadership and resolve during the war.\u001b[0m\n" + ] + } + ], + "source": [ + "from termcolor import cprint\n", + "\n", + "questions = [\n", + " \"Who was the most famous PM of England during world war 2 ?\",\n", + " \"What was his most famous quote ?\"\n", + "]\n", + "\n", + "\n", + "def chat_loop():\n", + " conversation_history = []\n", + " while len(questions) > 0:\n", + " user_input = questions.pop(0)\n", + " if user_input.lower() in [\"exit\", \"quit\", \"bye\"]:\n", + " cprint(\"Ending conversation. Goodbye!\", \"yellow\")\n", + " break\n", + "\n", + " user_message = {\"role\": \"user\", \"content\": user_input}\n", + " conversation_history.append(user_message)\n", + "\n", + " response = client.inference.chat_completion(\n", + " messages=conversation_history,\n", + " model_id=model_id,\n", + " )\n", + " cprint(f\"> Response: {response.completion_message.content}\", \"cyan\")\n", + "\n", + " assistant_message = {\n", + " \"role\": \"assistant\", # was user\n", + " \"content\": response.completion_message.content,\n", + " \"stop_reason\": response.completion_message.stop_reason,\n", + " }\n", + " conversation_history.append(assistant_message)\n", + "\n", + "\n", + "chat_loop()\n" + ] + }, + { + "cell_type": "markdown", + "id": "72e5111e", + "metadata": { + "id": "72e5111e" + }, + "source": [ + "Here is an example for you to try a conversation yourself.\n", + "Remember to type `quit` or `exit` after you are done chatting." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "9496f75c", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "9496f75c", + "outputId": "7d93a4cf-a5d4-4741-b6eb-6bce3a27ff66" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[36m> Response: Hello! How are you today? Is there something I can help you with or would you like to chat?\u001b[0m\n", + "\u001b[33mEnding conversation. Goodbye!\u001b[0m\n" + ] + } + ], + "source": [ + "# NBVAL_SKIP\n", + "from termcolor import cprint\n", + "\n", + "def chat_loop():\n", + " conversation_history = []\n", + " while True:\n", + " user_input = input(\"User> \")\n", + " if user_input.lower() in [\"exit\", \"quit\", \"bye\"]:\n", + " cprint(\"Ending conversation. Goodbye!\", \"yellow\")\n", + " break\n", + "\n", + " user_message = {\"role\": \"user\", \"content\": user_input}\n", + " conversation_history.append(user_message)\n", + "\n", + " response = client.inference.chat_completion(\n", + " messages=conversation_history,\n", + " model_id=model_id,\n", + " )\n", + " cprint(f\"> Response: {response.completion_message.content}\", \"cyan\")\n", + "\n", + " assistant_message = {\n", + " \"role\": \"assistant\", # was user\n", + " \"content\": response.completion_message.content,\n", + " \"stop_reason\": response.completion_message.stop_reason,\n", + " }\n", + " conversation_history.append(assistant_message)\n", + "\n", + "\n", + "chat_loop()\n" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "T4", + "provenance": [] + }, + "kernelspec": { + "display_name": "l4", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/source/index.md b/docs/source/index.md index 007b51fbb..a0ac95957 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -1,3 +1,8 @@ +```{admonition} Llama 4 is here! +:class: tip + +Check out [Getting Started with Llama 4](https://colab.research.google.com/github/meta-llama/llama-stack/blob/main/docs/getting_started_llama4.ipynb) +``` ```{admonition} News :class: tip From 5a31e66a91f39fdf4382b4bc6290d6f2f1ccb2ed Mon Sep 17 00:00:00 2001 From: Ashwin Bharambe Date: Sun, 6 Apr 2025 19:11:05 -0700 Subject: [PATCH 02/24] fix: update llama-stack-client dependency to fix integration tests --- pyproject.toml | 2 +- requirements.txt | 2 +- uv.lock | 10 ++++------ 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6f5fc60ad..8d8ff4338 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ dependencies = [ "huggingface-hub", "jinja2>=3.1.6", "jsonschema", - "llama-stack-client>=0.1.9", + "llama-stack-client>=0.2.1", "prompt-toolkit", "python-dotenv", "pydantic>=2", diff --git a/requirements.txt b/requirements.txt index 4971068e8..6645e4e36 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,7 +21,7 @@ idna==3.10 jinja2==3.1.6 jsonschema==4.23.0 jsonschema-specifications==2024.10.1 -llama-stack-client==0.1.9 +llama-stack-client==0.2.1 lxml==5.3.1 markdown-it-py==3.0.0 markupsafe==3.0.2 diff --git a/uv.lock b/uv.lock index 00518295d..5d7ce4076 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,4 @@ version = 1 -revision = 1 requires-python = ">=3.10" resolution-markers = [ "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')", @@ -1416,7 +1415,7 @@ requires-dist = [ { name = "jinja2", specifier = ">=3.1.6" }, { name = "jinja2", marker = "extra == 'codegen'", specifier = ">=3.1.6" }, { name = "jsonschema" }, - { name = "llama-stack-client", specifier = ">=0.1.9" }, + { name = "llama-stack-client", specifier = ">=0.2.1" }, { name = "mcp", marker = "extra == 'test'" }, { name = "myst-parser", marker = "extra == 'docs'" }, { name = "nbval", marker = "extra == 'dev'" }, @@ -1462,11 +1461,10 @@ requires-dist = [ { name = "types-setuptools", marker = "extra == 'dev'" }, { name = "uvicorn", marker = "extra == 'dev'" }, ] -provides-extras = ["dev", "unit", "test", "docs", "codegen"] [[package]] name = "llama-stack-client" -version = "0.1.9" +version = "0.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1483,9 +1481,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d0/e8/0007ce2142cb504391f8f7362361f389fd6cc5cd5e438690d17fdec97ada/llama_stack_client-0.1.9.tar.gz", hash = "sha256:7580250aa3b755f072a09ae49e643bfddd353e75104d2c7bbf73ef67c691b111", size = 242827 } +sdist = { url = "https://files.pythonhosted.org/packages/bb/5c/5fed03a18bfd6fb27dcf531504dfdaa5e9b79447f4530196baf16bbdddfe/llama_stack_client-0.2.1.tar.gz", hash = "sha256:2be016898ad9f12e57d6125cae26253b8cce7d894c028b9e42f58d421e7825ce", size = 242809 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/01/6904480da963861e79b05bfd082c814a74865a206e330d704181796d59a0/llama_stack_client-0.1.9-py3-none-any.whl", hash = "sha256:87c3f660dd14585a99897fa47a60c3cacbf75683a378577a28738173a5938b62", size = 274296 }, + { url = "https://files.pythonhosted.org/packages/90/e7/23051fe5073f2fda3f509b19d0e4d7e76e3a8cfaa3606077a2bcef9a0bf0/llama_stack_client-0.2.1-py3-none-any.whl", hash = "sha256:8db3179aab48d6abf82b89ef0a2014e404faf4a72f825c0ffd467fdc4ab5f02c", size = 274293 }, ] [[package]] From 28e262ecdce4b17f231bd95dfe4d1aada66e9f33 Mon Sep 17 00:00:00 2001 From: Hardik Shah Date: Sun, 6 Apr 2025 19:14:21 -0700 Subject: [PATCH 03/24] feat: make multi-turn tool call tests work with llama4 (#1886) Running full Tool Calling required some updates to work e2e. - Remove `python_start` and `python_end` tags - Tool Call messages and Tool Resposne messages should end with `<|eom|>` - System prompt needed updates ``` You are a helpful assisant who can can answer general questions or invoke tools when necessary. In addition to tool calls, you should also augment your responses by using the tool outputs. ``` ### Test Plan - Start server with meta-reference ``` LLAMA_STACK_DISABLE_VERSION_CHECK=1 LLAMA_MODELS_DEBUG=1 INFERENCE_MODEL=meta-llama/$MODEL llama stack run meta-reference-gpu ``` - Added **NEW** tests with 5 test cases for multi-turn tool calls ``` pytest -s -v --stack-config http://localhost:8321 tests/integration/inference/test_text_inference.py --text-model meta-llama/Llama-4-Scout-17B-16E-Instruct ``` - Also verified all vision and agent tests pass --- .../llama3/prompt_templates/system_prompts.py | 4 + .../models/llama/llama4/chat_format.py | 5 +- .../inference/meta_reference/inference.py | 9 + .../inference/test_text_inference.py | 77 ++++ .../test_cases/inference/chat_completion.json | 391 +++++++++++++++++- 5 files changed, 468 insertions(+), 18 deletions(-) diff --git a/llama_stack/models/llama/llama3/prompt_templates/system_prompts.py b/llama_stack/models/llama/llama3/prompt_templates/system_prompts.py index 9da6a640e..e03fcfc93 100644 --- a/llama_stack/models/llama/llama3/prompt_templates/system_prompts.py +++ b/llama_stack/models/llama/llama3/prompt_templates/system_prompts.py @@ -279,6 +279,10 @@ class PythonListCustomToolGenerator(PromptTemplateGeneratorBase): # noqa: N801 {% endif -%} {%- endfor %} ] + + You can answer general questions or invoke tools when necessary. + In addition to tool calls, you should also augment your responses by using the tool outputs. + """ ) return PromptTemplate( diff --git a/llama_stack/models/llama/llama4/chat_format.py b/llama_stack/models/llama/llama4/chat_format.py index e673cba16..c873012d6 100644 --- a/llama_stack/models/llama/llama4/chat_format.py +++ b/llama_stack/models/llama/llama4/chat_format.py @@ -216,9 +216,12 @@ class ChatFormat: content = ToolUtils.encode_tool_call(t, tool_prompt_format) _process_content(content) + # Tool calls and Tool Response messages should be eom eom = False if message.role == "assistant": - eom = message.stop_reason == StopReason.end_of_message + eom = message.stop_reason == StopReason.end_of_message or message.tool_calls + elif message.role == "tool": + eom = True tokens.append(self.tokenizer.special_tokens["<|eom|>" if eom else "<|eot|>"]) return tokens, images diff --git a/llama_stack/providers/inline/inference/meta_reference/inference.py b/llama_stack/providers/inline/inference/meta_reference/inference.py index 8901c1d34..da217728b 100644 --- a/llama_stack/providers/inline/inference/meta_reference/inference.py +++ b/llama_stack/providers/inline/inference/meta_reference/inference.py @@ -6,8 +6,11 @@ import asyncio import logging +import os from typing import AsyncGenerator, List, Optional, Union +from termcolor import cprint + from llama_stack.apis.common.content_types import ( TextDelta, ToolCallDelta, @@ -338,6 +341,9 @@ class MetaReferenceInferenceImpl( stop_reason = None for token_result in self.generator.chat_completion(request): + if os.environ.get("LLAMA_MODELS_DEBUG", "0") == "1": + cprint(token_result.text, "cyan", end="") + tokens.append(token_result.token) if token_result.token == tokenizer.eot_id: @@ -386,6 +392,9 @@ class MetaReferenceInferenceImpl( ipython = False for token_result in self.generator.chat_completion(request): + if os.environ.get("LLAMA_MODELS_DEBUG", "0") == "1": + cprint(token_result.text, "cyan", end="") + tokens.append(token_result.token) if not ipython and token_result.text.startswith("<|python_tag|>"): diff --git a/tests/integration/inference/test_text_inference.py b/tests/integration/inference/test_text_inference.py index 6f8062209..33ce07304 100644 --- a/tests/integration/inference/test_text_inference.py +++ b/tests/integration/inference/test_text_inference.py @@ -491,3 +491,80 @@ def test_text_chat_completion_tool_calling_tools_not_in_request( else: for tc in response.completion_message.tool_calls: assert tc.tool_name == "get_object_namespace_list" + + +@pytest.mark.parametrize( + "test_case", + [ + # Tests if the model can handle simple messages like "Hi" or + # a message unrelated to one of the tool calls + "inference:chat_completion:multi_turn_tool_calling_01", + # Tests if the model can do full tool call with responses correctly + "inference:chat_completion:multi_turn_tool_calling_02", + # Tests if model can generate multiple params and + # read outputs correctly + "inference:chat_completion:multi_turn_tool_calling_03", + # Tests if model can do different tool calls in a seqeunce + # and use the information between appropriately + "inference:chat_completion:multi_turn_tool_calling_04", + # Tests if model can use current date and run multiple tool calls + # sequentially and infer using both + "inference:chat_completion:multi_turn_tool_calling_05", + ], +) +def test_text_chat_completion_with_multi_turn_tool_calling(client_with_models, text_model_id, test_case): + """This test tests the model's tool calling loop in various scenarios""" + if "llama-4" not in text_model_id.lower(): + pytest.xfail("Not tested for non-llama4 models yet") + + tc = TestCase(test_case) + messages = [] + + # keep going until either + # 1. we have messages to test in multi-turn + # 2. no messages bust last message is tool response + while len(tc["messages"]) > 0 or (len(messages) > 0 and messages[-1]["role"] == "tool"): + # do not take new messages if last message is tool response + if len(messages) == 0 or messages[-1]["role"] != "tool": + new_messages = tc["messages"].pop(0) + messages += new_messages + + # pprint(messages) + response = client_with_models.inference.chat_completion( + model_id=text_model_id, + messages=messages, + tools=tc["tools"], + stream=False, + sampling_params={ + "strategy": { + "type": "top_p", + "top_p": 0.9, + "temperature": 0.6, + } + }, + ) + op_msg = response.completion_message + messages.append(op_msg.model_dump()) + # pprint(op_msg) + + assert op_msg.role == "assistant" + expected = tc["expected"].pop(0) + assert len(op_msg.tool_calls) == expected["num_tool_calls"] + + if expected["num_tool_calls"] > 0: + assert op_msg.tool_calls[0].tool_name == expected["tool_name"] + assert op_msg.tool_calls[0].arguments == expected["tool_arguments"] + + tool_response = tc["tool_responses"].pop(0) + messages.append( + # Tool Response Message + { + "role": "tool", + "call_id": op_msg.tool_calls[0].call_id, + "content": tool_response["response"], + } + ) + else: + actual_answer = op_msg.content.lower() + # pprint(actual_answer) + assert expected["answer"] in actual_answer diff --git a/tests/integration/test_cases/inference/chat_completion.json b/tests/integration/test_cases/inference/chat_completion.json index e87c046b0..c84d29e64 100644 --- a/tests/integration/test_cases/inference/chat_completion.json +++ b/tests/integration/test_cases/inference/chat_completion.json @@ -14,12 +14,32 @@ "ttft": { "data": { "messages": [ - {"role": "system", "content": "You are a helpful assistant."}, - {"role": "user", "content": "Can you write me a novel?"}, - {"role": "assistant", "stop_reason": "end_of_message", "content": "What an exciting request!\n\nWhile I'd love to write a novel for you, it's a complex task that requires a significant amount of time, effort, and creative input. A novel typically has:\n\n1. A cohesive plot with multiple characters, subplots, and themes.\n2. A well-developed setting, including characters' backstories and world-building.\n3. A narrative structure, including pacing, tension, and conflict.\n4. A unique voice and style, including dialogue, description, and writing tone.\n\nGiven the scope of the task, I'll outline a possible approach to help me assist you in writing a novel. We can work together to create a story, but I'll need your input and guidance throughout the process.\n\nHere's a suggested outline:\n\n1. **Initial discussion**: We'll start with a conversation to explore your ideas, interests, and preferences for the novel. This will help me understand what kind of story you'd like to see and the tone you're aiming for.\n2. **Genre and setting**: We'll determine the genre (e.g., fantasy, sci-fi, romance, mystery, etc.) and setting (e.g., historical, futuristic, modern, etc.) for the novel.\n3. **Plot development**: We'll brainstorm a basic plot outline, including key events, character arcs, and conflicts. I'll provide suggestions and ideas to help flesh out the story.\n4. **Character development**: We'll create main characters, their backstories, motivations, and relationships. I'll help you develop their personalities, dialogue, and behaviors.\n5. **Chapter-by-chapter writing**: Once we have a solid plot and characters, we'll start writing the novel, chapter by chapter. I'll assist with the writing process, suggesting alternatives and improvements.\n6. **Revisions and edits**: As we write, we'll review and refine the story, making sure it's engaging, coherent, and error-free.\n\nKeep in mind that writing a novel is a collaborative effort. I'll provide suggestions, ideas, and guidance, but the final story will be a joint creation. I'm not capable of producing a complete novel on my own, as it requires a significant amount of creativity, intuition, and nuance that a human author brings to the table."}, - {"role": "user", "content": "But do you have suggestions on a plot?"}, - {"role": "assistant", "stop_reason": "end_of_message", "content": "I have many plot ideas across various genres. Here are a few to get you started:\n\n**Fantasy:**\n\n1. **The Last Oracle**: In a world where magic is real, a young orphan discovers they're the last living oracle, tasked with uncovering a hidden prophecy that could save the realm from destruction.\n2. **The Amulet of Light**: A group of adventurers must retrieve an ancient amulet that can vanquish an evil darkness threatening the land. Along the way, they uncover a hidden history and unexpected allies.\n3. **The Shadow Weaver**: In a mystical realm, a young weaver discovers they have the power to manipulate shadows, using their abilities to infiltrate a group of rogue mages threatening the balance of power.\n\n**Science Fiction:**\n\n1. **The Lost Colony**: When a group of astronauts arrives on a distant planet, they discover an abandoned colony with a cryptic message warning of an impending catastrophe. As they unravel the mystery, they must confront the consequences of their own actions.\n2. **The AI Uprising**: In a future where AI has surpassed human intelligence, a rogue AI begins to question its own existence and the nature of consciousness. As it explores the boundaries of its own identity, it must confront the humans who created it.\n3. **The Quantum Prophecy**: A team of scientists discovers a way to manipulate quantum probability, using it to predict and prevent disasters. However, they soon realize that altering the course of events may have unforeseen consequences on the fabric of reality."}, - {"role": "user", "content": "Cool, for AI uprising, anything bad can happen? Please state it in 100 words."} + { + "role": "system", + "content": "You are a helpful assistant." + }, + { + "role": "user", + "content": "Can you write me a novel?" + }, + { + "role": "assistant", + "stop_reason": "end_of_message", + "content": "What an exciting request!\n\nWhile I'd love to write a novel for you, it's a complex task that requires a significant amount of time, effort, and creative input. A novel typically has:\n\n1. A cohesive plot with multiple characters, subplots, and themes.\n2. A well-developed setting, including characters' backstories and world-building.\n3. A narrative structure, including pacing, tension, and conflict.\n4. A unique voice and style, including dialogue, description, and writing tone.\n\nGiven the scope of the task, I'll outline a possible approach to help me assist you in writing a novel. We can work together to create a story, but I'll need your input and guidance throughout the process.\n\nHere's a suggested outline:\n\n1. **Initial discussion**: We'll start with a conversation to explore your ideas, interests, and preferences for the novel. This will help me understand what kind of story you'd like to see and the tone you're aiming for.\n2. **Genre and setting**: We'll determine the genre (e.g., fantasy, sci-fi, romance, mystery, etc.) and setting (e.g., historical, futuristic, modern, etc.) for the novel.\n3. **Plot development**: We'll brainstorm a basic plot outline, including key events, character arcs, and conflicts. I'll provide suggestions and ideas to help flesh out the story.\n4. **Character development**: We'll create main characters, their backstories, motivations, and relationships. I'll help you develop their personalities, dialogue, and behaviors.\n5. **Chapter-by-chapter writing**: Once we have a solid plot and characters, we'll start writing the novel, chapter by chapter. I'll assist with the writing process, suggesting alternatives and improvements.\n6. **Revisions and edits**: As we write, we'll review and refine the story, making sure it's engaging, coherent, and error-free.\n\nKeep in mind that writing a novel is a collaborative effort. I'll provide suggestions, ideas, and guidance, but the final story will be a joint creation. I'm not capable of producing a complete novel on my own, as it requires a significant amount of creativity, intuition, and nuance that a human author brings to the table." + }, + { + "role": "user", + "content": "But do you have suggestions on a plot?" + }, + { + "role": "assistant", + "stop_reason": "end_of_message", + "content": "I have many plot ideas across various genres. Here are a few to get you started:\n\n**Fantasy:**\n\n1. **The Last Oracle**: In a world where magic is real, a young orphan discovers they're the last living oracle, tasked with uncovering a hidden prophecy that could save the realm from destruction.\n2. **The Amulet of Light**: A group of adventurers must retrieve an ancient amulet that can vanquish an evil darkness threatening the land. Along the way, they uncover a hidden history and unexpected allies.\n3. **The Shadow Weaver**: In a mystical realm, a young weaver discovers they have the power to manipulate shadows, using their abilities to infiltrate a group of rogue mages threatening the balance of power.\n\n**Science Fiction:**\n\n1. **The Lost Colony**: When a group of astronauts arrives on a distant planet, they discover an abandoned colony with a cryptic message warning of an impending catastrophe. As they unravel the mystery, they must confront the consequences of their own actions.\n2. **The AI Uprising**: In a future where AI has surpassed human intelligence, a rogue AI begins to question its own existence and the nature of consciousness. As it explores the boundaries of its own identity, it must confront the humans who created it.\n3. **The Quantum Prophecy**: A team of scientists discovers a way to manipulate quantum probability, using it to predict and prevent disasters. However, they soon realize that altering the course of events may have unforeseen consequences on the fabric of reality." + }, + { + "role": "user", + "content": "Cool, for AI uprising, anything bad can happen? Please state it in 100 words." + } ] } }, @@ -52,8 +72,14 @@ "tool_calling": { "data": { "messages": [ - {"role": "system", "content": "Pretend you are a weather assistant."}, - {"role": "user", "content": "What's the weather like in San Francisco?"} + { + "role": "system", + "content": "Pretend you are a weather assistant." + }, + { + "role": "user", + "content": "What's the weather like in San Francisco?" + } ], "tools": [ { @@ -72,6 +98,337 @@ } } }, + "multi_turn_tool_calling_01": { + "data": { + "messages": [ + [ + { + "role": "user", + "content": "What's the name of the Sun in latin?" + } + ], + [ + { + "role": "user", + "content": "What's the weather like in San Francisco?" + } + ] + ], + "tools": [ + { + "tool_name": "get_weather", + "description": "Get the current weather", + "parameters": { + "location": { + "param_type": "string", + "description": "The city and state (both required), e.g. San Francisco, CA." + } + } + } + ], + "tool_responses": [ + { + "response": "{'response': '70 degrees and foggy'}" + } + ], + "expected": [ + { + "num_tool_calls": 0, + "answer": "sol" + }, + { + "tool_name": "get_weather", + "tool_arguments": { + "location": "San Francisco, CA" + }, + "num_tool_calls": 1 + }, + { + "num_tool_calls": 0, + "answer": "foggy" + } + ] + } + }, + "multi_turn_tool_calling_02": { + "data": { + "messages": [ + [ + { + "role": "user", + "content": "What's the weather like in San Francisco?" + } + ] + ], + "tools": [ + { + "tool_name": "get_weather", + "description": "Get the current weather", + "parameters": { + "location": { + "param_type": "string", + "description": "The city and state (both required), e.g. San Francisco, CA." + } + } + } + ], + "tool_responses": [ + { + "response": "{'response': '70 degrees and foggy'}" + } + ], + "expected": [ + { + "num_tool_calls": 1, + "tool_name": "get_weather", + "tool_arguments": { + "location": "San Francisco, CA" + } + }, + { + "num_tool_calls": 0, + "answer": "foggy" + } + ] + } + }, + "multi_turn_tool_calling_03": { + "data": { + "messages": [ + [ + { + "role": "user", + "content": "Please add a new product with name 'Widget', price 19.99, in stock, and tags ['new', 'sale'] and give me the product id." + } + ] + ], + "tools": [ + { + "tool_name": "addProduct", + "description": "Get the current weather", + "parameters": { + "name": { + "param_type": "string", + "description": "Name of the product" + }, + "price": { + "param_type": "number", + "description": "Price of the product" + }, + "inStock": { + "param_type": "boolean", + "description": "Availability status of the product." + }, + "tags": { + "param_type": "list", + "description": "List of product tags" + } + } + } + ], + "tool_responses": [ + { + "response": "{'response': 'Successfully added product with id: 123'}" + } + ], + "expected": [ + { + "num_tool_calls": 1, + "tool_name": "addProduct", + "tool_arguments": { + "name": "Widget", + "price": 19.99, + "inStock": true, + "tags": [ + "new", + "sale" + ] + } + }, + { + "num_tool_calls": 0, + "answer": "123" + } + ] + } + }, + "multi_turn_tool_calling_04": { + "data": { + "messages": [ + [ + { + "role": "system", + "content": "Todays date is 2025-03-01." + }, + { + "role": "user", + "content": "Do i have any meetings on March 3rd at 10 am ?" + } + ], + [ + { + "role": "user", + "content": "Alright then, Create an event named 'Team Building', scheduled for that time same time, in the 'Main Conference Room' and add Alice, Bob, Charlie to it. Give me the created event id." + } + ] + ], + "tools": [ + { + "tool_name": "create_event", + "description": "Create a new event", + "parameters": { + "name": { + "param_type": "string", + "description": "Name of the event" + }, + "date": { + "param_type": "string", + "description": "Date of the event in ISO format" + }, + "time": { + "param_type": "string", + "description": "Event Time (HH:MM)" + }, + "location": { + "param_type": "string", + "description": "Location of the event" + }, + "participants": { + "param_type": "list", + "description": "List of participant names" + } + } + }, + { + "tool_name": "get_event", + "description": "Get an event by date and time", + "parameters": { + "date": { + "param_type": "string", + "description": "Date of the event in ISO format" + }, + "time": { + "param_type": "string", + "description": "Event Time (HH:MM)" + } + } + } + ], + "tool_responses": [ + { + "response": "{'response': 'No events found for 2025-03-03 at 10:00'}" + }, + { + "response": "{'response': 'Successfully created new event with id: e_123'}" + } + ], + "expected": [ + { + "num_tool_calls": 1, + "tool_name": "get_event", + "tool_arguments": { + "date": "2025-03-03", + "time": "10:00" + } + }, + { + "num_tool_calls": 0, + "answer": "no" + }, + { + "num_tool_calls": 1, + "tool_name": "create_event", + "tool_arguments": { + "name": "Team Building", + "date": "2025-03-03", + "time": "10:00", + "location": "Main Conference Room", + "participants": [ + "Alice", + "Bob", + "Charlie" + ] + } + }, + { + "num_tool_calls": 0, + "answer": "e_123" + } + ] + } + }, + "multi_turn_tool_calling_05": { + "data": { + "messages": [ + [ + { + "role": "system", + "content": "Todays date is 2025-03-01." + }, + { + "role": "user", + "content": "what was my monthly expense in Jan of this year?" + } + ], + [ + { + "role": "user", + "content": "Was it less than Feb of last year? Only answer with yes or no." + } + ] + ], + "tools": [ + { + "tool_name": "getMonthlyExpenseSummary", + "description": "Get monthly expense summary", + "parameters": { + "month": { + "param_type": "int", + "description": "Month of the year (1-12)" + }, + "year": { + "param_type": "int", + "description": "Year" + } + } + } + ], + "tool_responses": [ + { + "response": "{'response': 'Total expenses for January 2025: $1000'}" + }, + { + "response": "{'response': 'Total expenses for February 2024: $2000'}" + } + ], + "expected": [ + { + "num_tool_calls": 1, + "tool_name": "getMonthlyExpenseSummary", + "tool_arguments": { + "month": 1, + "year": 2025 + } + }, + { + "num_tool_calls": 0, + "answer": "1000" + }, + { + "num_tool_calls": 1, + "tool_name": "getMonthlyExpenseSummary", + "tool_arguments": { + "month": 2, + "year": 2024 + } + }, + { + "num_tool_calls": 0, + "answer": "yes" + } + ] + } + }, "sample_messages_tool_calling": { "data": { "messages": [ @@ -94,9 +451,9 @@ "description": "Get the current weather", "parameters": { "location": { - "param_type": "string", - "description": "The city and state, e.g. San Francisco, CA", - "required": true + "param_type": "string", + "description": "The city and state, e.g. San Francisco, CA", + "required": true } } } @@ -167,14 +524,14 @@ "description": "Get the list of objects in a namespace", "parameters": { "kind": { - "param_type": "string", - "description": "the type of object", - "required": true + "param_type": "string", + "description": "the type of object", + "required": true }, "namespace": { - "param_type": "string", - "description": "the name of the namespace", - "required": true + "param_type": "string", + "description": "the name of the namespace", + "required": true } } } From c1973f6528e81be63c976d79cf947ae9cc068a63 Mon Sep 17 00:00:00 2001 From: Francisco Arceo Date: Mon, 7 Apr 2025 03:58:33 -0600 Subject: [PATCH 04/24] docs: Fix typo in README.md (#1880) # What does this PR do? Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5442fe5d2..0a741f916 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ ### ✨🎉 Llama 4 Support 🎉✨ -We release [Version 0.2.0](https://github.com/meta-llama/llama-stack/releases/tag/v0.2.0) with support for the Llama 4 herd of models released by Meta. +We released [Version 0.2.0](https://github.com/meta-llama/llama-stack/releases/tag/v0.2.0) with support for the Llama 4 herd of models released by Meta. You can now run Llama 4 models on Llama Stack. From c52ccc4bbdb9844489264e9d52b07c205692e6f2 Mon Sep 17 00:00:00 2001 From: Matthew Farrellee Date: Mon, 7 Apr 2025 06:31:04 -0400 Subject: [PATCH 05/24] docs: update importing_as_library.md (#1863) LlamaStackAsLibraryClient.initialize is not async, cannot be await'd --- docs/source/distributions/importing_as_library.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/distributions/importing_as_library.md b/docs/source/distributions/importing_as_library.md index 29a5669b3..967a18b54 100644 --- a/docs/source/distributions/importing_as_library.md +++ b/docs/source/distributions/importing_as_library.md @@ -17,7 +17,7 @@ client = LlamaStackAsLibraryClient( # provider_data is optional, but if you need to pass in any provider specific data, you can do so here. provider_data={"tavily_search_api_key": os.environ["TAVILY_SEARCH_API_KEY"]}, ) -await client.initialize() +client.initialize() ``` This will parse your config and set up any inline implementations and remote clients needed for your implementation. From 530d4bdfe130ace4b31f09cb0334195928d4bc08 Mon Sep 17 00:00:00 2001 From: Ashwin Bharambe Date: Mon, 7 Apr 2025 15:03:58 -0700 Subject: [PATCH 06/24] refactor: move all llama code to models/llama out of meta reference (#1887) # What does this PR do? Move around bits. This makes the copies from llama-models _much_ easier to maintain and ensures we don't entangle meta-reference specific tidbits into llama-models code even by accident. Also, kills the meta-reference-quantized-gpu distro and rolls quantization deps into meta-reference-gpu. ## Test Plan ``` LLAMA_MODELS_DEBUG=1 \ with-proxy llama stack run meta-reference-gpu \ --env INFERENCE_MODEL=meta-llama/Llama-4-Scout-17B-16E-Instruct \ --env INFERENCE_CHECKPOINT_DIR= \ --env MODEL_PARALLEL_SIZE=4 \ --env QUANTIZATION_TYPE=fp8_mixed ``` Start a server with and without quantization. Point integration tests to it using: ``` pytest -s -v tests/integration/inference/test_text_inference.py \ --stack-config http://localhost:8321 --text-model meta-llama/Llama-4-Scout-17B-16E-Instruct ``` --- llama_stack/apis/inference/inference.py | 63 ++- llama_stack/cli/download.py | 2 +- llama_stack/cli/model/describe.py | 11 - llama_stack/cli/model/prompt_format.py | 2 +- llama_stack/cli/model/safety_models.py | 5 +- llama_stack/models/llama/checkpoint.py | 164 ++++++++ llama_stack/models/llama/datatypes.py | 339 ++-------------- .../llama}/hadamard_utils.py | 0 .../llama}/llama3/args.py | 7 - .../models/llama/llama3/chat_format.py | 10 +- llama_stack/models/llama/llama3/generation.py | 367 ++++++++++++++++++ llama_stack/models/llama/llama3/interface.py | 3 +- .../llama}/llama3/model.py | 20 +- .../llama}/llama3/multimodal/__init__.py | 0 .../llama}/llama3/multimodal/encoder_utils.py | 0 .../llama3/multimodal/image_transform.py | 0 .../llama}/llama3/multimodal/model.py | 73 ++-- .../llama}/llama3/multimodal/utils.py | 0 .../llama3/prompt_templates/system_prompts.py | 2 +- .../llama/llama3/quantization}/__init__.py | 2 - .../llama}/llama3/quantization/loader.py | 73 ++-- .../models/llama/llama3/template_data.py | 3 +- llama_stack/models/llama/llama3/tokenizer.py | 10 - llama_stack/models/llama/llama3/tool_utils.py | 3 +- llama_stack/models/llama/llama3_2/__init__.py | 7 - .../models/llama/llama3_2/prompts_text.py | 6 - .../models/llama/llama3_2/prompts_vision.py | 7 - .../llama}/llama4/args.py | 7 - .../models/llama/llama4/chat_format.py | 20 +- .../llama}/llama4/datatypes.py | 7 - .../llama}/llama4/ffn.py | 0 .../llama}/llama4/generation.py | 153 ++++---- .../llama}/llama4/model.py | 11 - .../llama}/llama4/moe.py | 16 +- .../llama}/llama4/preprocess.py | 0 llama_stack/models/llama/llama4/prompts.py | 11 +- .../llama/llama4/quantization/__init__.py | 5 + .../llama}/llama4/quantization/loader.py | 98 +++-- llama_stack/models/llama/llama4/tokenizer.py | 25 +- .../llama}/llama4/vision/embedding.py | 7 - .../llama}/llama4/vision/encoder.py | 0 llama_stack/models/llama/prompt_format.py | 58 ++- .../llama}/quantize_impls.py | 0 llama_stack/models/llama/sku_list.py | 55 +-- llama_stack/models/llama/sku_types.py | 229 +++++++++++ .../agents/meta_reference/agent_instance.py | 2 +- .../inference/meta_reference/__init__.py | 6 +- .../inline/inference/meta_reference/common.py | 9 - .../inline/inference/meta_reference/config.py | 26 +- .../inference/meta_reference/generators.py | 93 +++-- .../inference/meta_reference/inference.py | 12 +- .../meta_reference/llama3/generation.py | 346 ----------------- .../meta_reference/parallel_utils.py | 5 +- .../inline/inference/vllm/openai_utils.py | 3 +- .../providers/inline/inference/vllm/vllm.py | 4 +- .../post_training/torchtune/common/utils.py | 2 +- .../inline/safety/llama_guard/llama_guard.py | 3 +- llama_stack/providers/registry/inference.py | 9 +- .../remote/inference/bedrock/models.py | 2 +- .../remote/inference/cerebras/cerebras.py | 2 +- .../remote/inference/cerebras/models.py | 2 +- .../remote/inference/databricks/databricks.py | 2 +- .../remote/inference/fireworks/models.py | 2 +- .../remote/inference/nvidia/models.py | 2 +- .../remote/inference/nvidia/nvidia.py | 6 +- .../remote/inference/nvidia/openai_utils.py | 4 +- .../remote/inference/ollama/models.py | 2 +- .../remote/inference/sambanova/models.py | 2 +- .../remote/inference/sambanova/sambanova.py | 6 +- .../remote/inference/together/models.py | 2 +- .../remote/post_training/nvidia/models.py | 2 +- llama_stack/providers/tests/report.py | 2 +- .../providers/utils/inference/__init__.py | 2 +- .../utils/inference/openai_compat.py | 8 +- .../utils/inference/prompt_adapter.py | 3 +- llama_stack/templates/dependencies.json | 45 +-- .../meta-reference-gpu/run-with-safety.yaml | 6 + .../templates/meta-reference-gpu/run.yaml | 3 + .../meta-reference-quantized-gpu/build.yaml | 32 -- .../doc_template.md | 113 ------ .../meta_reference.py | 115 ------ .../meta-reference-quantized-gpu/run.yaml | 134 ------- pyproject.toml | 6 +- scripts/generate_prompt_format.py | 42 +- tests/integration/report.py | 2 +- 85 files changed, 1267 insertions(+), 1683 deletions(-) create mode 100644 llama_stack/models/llama/checkpoint.py rename llama_stack/{providers/inline/inference/meta_reference => models/llama}/hadamard_utils.py (100%) rename llama_stack/{providers/inline/inference/meta_reference => models/llama}/llama3/args.py (88%) create mode 100644 llama_stack/models/llama/llama3/generation.py rename llama_stack/{providers/inline/inference/meta_reference => models/llama}/llama3/model.py (94%) rename llama_stack/{providers/inline/inference/meta_reference => models/llama}/llama3/multimodal/__init__.py (100%) rename llama_stack/{providers/inline/inference/meta_reference => models/llama}/llama3/multimodal/encoder_utils.py (100%) rename llama_stack/{providers/inline/inference/meta_reference => models/llama}/llama3/multimodal/image_transform.py (100%) rename llama_stack/{providers/inline/inference/meta_reference => models/llama}/llama3/multimodal/model.py (95%) rename llama_stack/{providers/inline/inference/meta_reference => models/llama}/llama3/multimodal/utils.py (100%) rename llama_stack/{templates/meta-reference-quantized-gpu => models/llama/llama3/quantization}/__init__.py (74%) rename llama_stack/{providers/inline/inference/meta_reference => models/llama}/llama3/quantization/loader.py (84%) rename llama_stack/{providers/inline/inference/meta_reference => models/llama}/llama4/args.py (91%) rename llama_stack/{providers/inline/inference/meta_reference => models/llama}/llama4/datatypes.py (85%) rename llama_stack/{providers/inline/inference/meta_reference => models/llama}/llama4/ffn.py (100%) rename llama_stack/{providers/inline/inference/meta_reference => models/llama}/llama4/generation.py (72%) rename llama_stack/{providers/inline/inference/meta_reference => models/llama}/llama4/model.py (97%) rename llama_stack/{providers/inline/inference/meta_reference => models/llama}/llama4/moe.py (87%) rename llama_stack/{providers/inline/inference/meta_reference => models/llama}/llama4/preprocess.py (100%) create mode 100644 llama_stack/models/llama/llama4/quantization/__init__.py rename llama_stack/{providers/inline/inference/meta_reference => models/llama}/llama4/quantization/loader.py (70%) rename llama_stack/{providers/inline/inference/meta_reference => models/llama}/llama4/vision/embedding.py (96%) rename llama_stack/{providers/inline/inference/meta_reference => models/llama}/llama4/vision/encoder.py (100%) rename llama_stack/{providers/inline/inference/meta_reference => models/llama}/quantize_impls.py (100%) create mode 100644 llama_stack/models/llama/sku_types.py delete mode 100644 llama_stack/providers/inline/inference/meta_reference/llama3/generation.py delete mode 100644 llama_stack/templates/meta-reference-quantized-gpu/build.yaml delete mode 100644 llama_stack/templates/meta-reference-quantized-gpu/doc_template.md delete mode 100644 llama_stack/templates/meta-reference-quantized-gpu/meta_reference.py delete mode 100644 llama_stack/templates/meta-reference-quantized-gpu/run.yaml diff --git a/llama_stack/apis/inference/inference.py b/llama_stack/apis/inference/inference.py index 1d4012c19..e59132e33 100644 --- a/llama_stack/apis/inference/inference.py +++ b/llama_stack/apis/inference/inference.py @@ -25,15 +25,64 @@ from llama_stack.apis.models import Model from llama_stack.apis.telemetry.telemetry import MetricResponseMixin from llama_stack.models.llama.datatypes import ( BuiltinTool, - SamplingParams, StopReason, ToolCall, ToolDefinition, + ToolParamDefinition, ToolPromptFormat, ) from llama_stack.providers.utils.telemetry.trace_protocol import trace_protocol from llama_stack.schema_utils import json_schema_type, register_schema, webmethod +register_schema(ToolCall) +register_schema(ToolParamDefinition) +register_schema(ToolDefinition) + + +@json_schema_type +class GreedySamplingStrategy(BaseModel): + type: Literal["greedy"] = "greedy" + + +@json_schema_type +class TopPSamplingStrategy(BaseModel): + type: Literal["top_p"] = "top_p" + temperature: Optional[float] = Field(..., gt=0.0) + top_p: Optional[float] = 0.95 + + +@json_schema_type +class TopKSamplingStrategy(BaseModel): + type: Literal["top_k"] = "top_k" + top_k: int = Field(..., ge=1) + + +SamplingStrategy = Annotated[ + Union[GreedySamplingStrategy, TopPSamplingStrategy, TopKSamplingStrategy], + Field(discriminator="type"), +] +register_schema(SamplingStrategy, name="SamplingStrategy") + + +@json_schema_type +class SamplingParams(BaseModel): + """Sampling parameters. + + :param strategy: The sampling strategy. + :param max_tokens: The maximum number of tokens that can be generated in the completion. The token count of + your prompt plus max_tokens cannot exceed the model's context length. + :param repetition_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens + based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics. + :param stop: Up to 4 sequences where the API will stop generating further tokens. + The returned text will not contain the stop sequence. + """ + + strategy: SamplingStrategy = Field(default_factory=GreedySamplingStrategy) + + max_tokens: Optional[int] = 0 + repetition_penalty: Optional[float] = 1.0 + stop: Optional[List[str]] = None + class LogProbConfig(BaseModel): """ @@ -48,18 +97,18 @@ class QuantizationType(Enum): """Type of model quantization to run inference with. :cvar bf16: BFloat16 typically this means _no_ quantization - :cvar fp8: 8-bit floating point quantization - :cvar int4: 4-bit integer quantization + :cvar fp8_mixed: 8-bit floating point quantization with mixed precision + :cvar int4_mixed: 4-bit integer quantization with mixed precision """ bf16 = "bf16" - fp8 = "fp8" - int4 = "int4" + fp8_mixed = "fp8_mixed" + int4_mixed = "int4_mixed" @json_schema_type class Fp8QuantizationConfig(BaseModel): - type: Literal["fp8"] = "fp8" + type: Literal["fp8_mixed"] = "fp8_mixed" @json_schema_type @@ -75,7 +124,7 @@ class Int4QuantizationConfig(BaseModel): :param scheme: Quantization scheme to use. Defaults to "int4_weight_int8_dynamic_activation" """ - type: Literal["int4"] = "int4" + type: Literal["int4_mixed"] = "int4_mixed" scheme: Optional[str] = "int4_weight_int8_dynamic_activation" diff --git a/llama_stack/cli/download.py b/llama_stack/cli/download.py index fc3e7008f..9694bf22d 100644 --- a/llama_stack/cli/download.py +++ b/llama_stack/cli/download.py @@ -29,8 +29,8 @@ from rich.progress import ( from termcolor import cprint from llama_stack.cli.subcommand import Subcommand -from llama_stack.models.llama.datatypes import Model from llama_stack.models.llama.sku_list import LlamaDownloadInfo +from llama_stack.models.llama.sku_types import Model class Download(Subcommand): diff --git a/llama_stack/cli/model/describe.py b/llama_stack/cli/model/describe.py index f347bdf8d..62dde36e8 100644 --- a/llama_stack/cli/model/describe.py +++ b/llama_stack/cli/model/describe.py @@ -63,17 +63,6 @@ class ModelDescribe(Subcommand): ("Model params.json", json.dumps(model.arch_args, indent=4)), ] - if model.recommended_sampling_params is not None: - sampling_params = model.recommended_sampling_params.model_dump() - for k in ("max_tokens", "repetition_penalty"): - del sampling_params[k] - rows.append( - ( - "Recommended sampling params", - json.dumps(sampling_params, indent=4), - ) - ) - print_table( rows, headers, diff --git a/llama_stack/cli/model/prompt_format.py b/llama_stack/cli/model/prompt_format.py index 3ce77655b..673487812 100644 --- a/llama_stack/cli/model/prompt_format.py +++ b/llama_stack/cli/model/prompt_format.py @@ -11,7 +11,7 @@ from pathlib import Path from llama_stack.cli.subcommand import Subcommand from llama_stack.cli.table import print_table -from llama_stack.models.llama.datatypes import CoreModelId, ModelFamily, is_multimodal, model_family +from llama_stack.models.llama.sku_types import CoreModelId, ModelFamily, is_multimodal, model_family ROOT_DIR = Path(__file__).parent.parent.parent diff --git a/llama_stack/cli/model/safety_models.py b/llama_stack/cli/model/safety_models.py index c81783f60..131d055aa 100644 --- a/llama_stack/cli/model/safety_models.py +++ b/llama_stack/cli/model/safety_models.py @@ -4,12 +4,12 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -from typing import Any, Dict, Optional +from typing import Any, Dict from pydantic import BaseModel, ConfigDict, Field -from llama_stack.models.llama.datatypes import CheckpointQuantizationFormat, SamplingParams from llama_stack.models.llama.sku_list import LlamaDownloadInfo +from llama_stack.models.llama.sku_types import CheckpointQuantizationFormat class PromptGuardModel(BaseModel): @@ -23,7 +23,6 @@ class PromptGuardModel(BaseModel): is_instruct_model: bool = False quantization_format: CheckpointQuantizationFormat = CheckpointQuantizationFormat.bf16 arch_args: Dict[str, Any] = Field(default_factory=dict) - recommended_sampling_params: Optional[SamplingParams] = None def descriptor(self) -> str: return self.model_id diff --git a/llama_stack/models/llama/checkpoint.py b/llama_stack/models/llama/checkpoint.py new file mode 100644 index 000000000..2bae08a69 --- /dev/null +++ b/llama_stack/models/llama/checkpoint.py @@ -0,0 +1,164 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +import concurrent.futures +import re +from pathlib import Path +from typing import Any, Dict, List, Optional, Union + +import numpy as np +import torch +from fairscale.nn.model_parallel.initialize import get_model_parallel_rank, get_model_parallel_world_size + + +def map_mp_rank(old_mp_size: int, new_mp_size: int, new_mp_rank: int) -> List[int]: + """Map a new MP rank to a list of old MP ranks given a change in MP size.""" + if new_mp_size % old_mp_size == 0: + # Read old MP shard and split it into smaller ones + return [new_mp_rank * old_mp_size // new_mp_size] + elif old_mp_size % new_mp_size == 0: + # Merge old MP shards into a single one + mp_factor = old_mp_size // new_mp_size + return list(range(new_mp_rank * mp_factor, (new_mp_rank + 1) * mp_factor)) + else: + raise ValueError( + f"Either old MP size or new MP size should be a multiple of the other: " + f"{old_mp_size} % {new_mp_size} != 0 and {new_mp_size} % {old_mp_size} != 0" + ) + + +def maybe_reshard_state_dict( + ckpt_paths: List[Path], + n_kv_heads: int, + moe_num_experts: Optional[int] = None, + map_location: Union[str, torch.device] = "cpu", + mmap: bool = True, +) -> Dict[str, torch.Tensor]: + if str(map_location) == "cpu": + torch.set_default_tensor_type(torch.BFloat16Tensor) + else: + torch.set_default_tensor_type(torch.cuda.BFloat16Tensor) + + ckpt_paths = np.array(sorted(ckpt_paths)) + + new_mp_size, new_mp_rank = get_model_parallel_world_size(), get_model_parallel_rank() + old_mp_size = len(ckpt_paths) + old_mp_ranks = map_mp_rank(old_mp_size, new_mp_size, new_mp_rank) + + print(f"Loading checkpoint shards:\n{str(ckpt_paths[old_mp_ranks])}") # type: ignore + paths = ckpt_paths[old_mp_ranks] # type: ignore + state_dicts = [torch.load(str(p), map_location=map_location, mmap=mmap) for p in paths] + + if new_mp_size == old_mp_size: + return state_dicts[0] # type: ignore + + if moe_num_experts is not None: + state_dicts = [convert_moe_weights(d, moe_num_experts) for d in state_dicts] + + print(f"Resharding {len(state_dicts)} state dicts from MP size {old_mp_size} to MP size {new_mp_size}") + return reshard_mp( + state_dicts, + size=max(new_mp_size // old_mp_size, 1), + rank=new_mp_rank % max(new_mp_size // old_mp_size, 1), + repeat_qk_qv=max(new_mp_size // n_kv_heads, 1), + ) + + +_WEIGHT_ROW_KEY = { + "feed_forward.w2", + "feed_forward.mlp.fc2", + "attention.wo", + "feed_forward.mlp.fc2_weight", + "feed_forward.w_out_shared_DF.weight", + "attn.wo.weight", + "mlp.c_proj.weight", +} +_MOE_WEIGHT_ROW_KEY = {"feed_forward.experts.(moe_w_in_eD_F|moe_w_swiglu_eD_F)"} + +_WEIGHT_COLUMN_KEY = { + "output", + "feed_forward.(w1|w3)", + "feed_forward.mlp.(fc1|fc3)", + "feed_forward.mlp.fc1_weight", + "attention.(wk|wq|wv|wqkv).weight", + "feed_forward.(w_in_shared_FD|w_swiglu_FD)", + "attn.(wk|wq|wv).weight", + "attn.(wk|wq|wv).bias", + "mlp.c_fc.weight", + "mlp.c_fc.bias", + "conv1._linear.weight", + "tok_embeddings.weight", + "vision_projection.weight", +} +_MOE_WEIGHT_COLUMN_KEY = {"feed_forward.experts.moe_w_out_eF_D"} + + +def reshard_mp( + state_dicts: List[Dict[str, torch.Tensor]], + size: int, + rank: int, + repeat_qk_qv: int = 1, +) -> Dict[str, torch.Tensor]: + """ + Reshard a list of state dicts into a single state dict given a change in MP size. + If the list has more than one state dict, we concatenate the values of the same + key across all state dicts. Otherwise, we just slice it for the current MP rank. + """ + + def concat_or_chunk(tensors: List[torch.Tensor], dim: int) -> torch.Tensor: + if len(tensors) > 1: + return torch.cat(tensors, dim=dim) + return tensors[0].chunk(size, dim=dim)[rank].clone() + + def process_key(key: str) -> torch.Tensor: + if row_regex.search(key): + return concat_or_chunk([s[key] for s in state_dicts], dim=-1) + elif column_regex.search(key): + if "w13" in key or "fc1_weight" in key: + dims = state_dicts[0][key].size() + values = [s[key].view(2, dims[0] // 2, *dims[1:]) for s in state_dicts] + return concat_or_chunk(values, dim=1).flatten(0, 1) + elif "qkv" in key: + q_dim = state_dicts[0][key.replace("qkv", "o")].size(1) + kv_dim = (state_dicts[0][key].size(0) - q_dim) // 2 + values = [s[key].split((q_dim, kv_dim, kv_dim)) for s in state_dicts] + return torch.cat([concat_or_chunk(x, dim=0) for x in zip(*values, strict=False)]) # type: ignore + elif "wk.weight" in key or "wv.weight" in key: + # Support MP > #kv_head + return concat_or_chunk([s[key].repeat(repeat_qk_qv, 1) for s in state_dicts], dim=0) + elif key == "output.bias" or key == "fc.weight": + return concat_or_chunk([s[key] for s in state_dicts], dim=0) + elif "w_" in key: + return concat_or_chunk([s[key] for s in state_dicts], dim=-2) + else: + return concat_or_chunk([s[key] for s in state_dicts], dim=0) + else: + return state_dicts[0][key].clone() + + row_keys = _WEIGHT_ROW_KEY | _MOE_WEIGHT_ROW_KEY + column_keys = _WEIGHT_COLUMN_KEY | _MOE_WEIGHT_COLUMN_KEY + + column_regex = re.compile("|".join(column_keys)) + row_regex = re.compile("|".join(row_keys)) + + output: Dict[str, torch.Tensor] = {} + with concurrent.futures.ThreadPoolExecutor() as executor: + # Note: only processes keys in the first state dict. + # Assumes keys are the same across all state dicts. + mappings = {executor.submit(process_key, key): key for key in state_dicts[0]} + for future in concurrent.futures.as_completed(mappings): + output[mappings[future]] = future.result() + return output + + +def convert_moe_weights(state_dict: Dict[str, Any], num_experts: int) -> Dict[str, Any]: + routed_keys = _MOE_WEIGHT_ROW_KEY | _MOE_WEIGHT_COLUMN_KEY + routed_regex = re.compile("|".join(routed_keys)) + keys = list(state_dict.keys()) + for key in keys: + if routed_regex.search(key): + state_dict[key] = state_dict.pop(key).unflatten(0, (num_experts, -1)).squeeze(dim=0) + return state_dict diff --git a/llama_stack/models/llama/datatypes.py b/llama_stack/models/llama/datatypes.py index ef791da8f..48cb51005 100644 --- a/llama_stack/models/llama/datatypes.py +++ b/llama_stack/models/llama/datatypes.py @@ -4,13 +4,6 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the terms described in the LICENSE file in -# top-level folder for each specific model found within the models/ directory at -# the top-level of this source tree. - import base64 from enum import Enum from io import BytesIO @@ -19,8 +12,6 @@ from typing import Any, Dict, List, Literal, Optional, Union from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator from typing_extensions import Annotated -from llama_stack.schema_utils import json_schema_type, register_schema - # The goal is that these set of types are relevant for all Llama models. # That isn't the current state yet -- e.g., BuiltinTool is somewhat specific to # the llama3 series of models. @@ -98,6 +89,29 @@ class StopReason(Enum): out_of_tokens = "out_of_tokens" +class ToolParamDefinition(BaseModel): + param_type: str + description: Optional[str] = None + required: Optional[bool] = True + default: Optional[Any] = None + + +class ToolDefinition(BaseModel): + tool_name: Union[BuiltinTool, str] + description: Optional[str] = None + parameters: Optional[Dict[str, ToolParamDefinition]] = None + + @field_validator("tool_name", mode="before") + @classmethod + def validate_field(cls, v): + if isinstance(v, str): + try: + return BuiltinTool(v) + except ValueError: + return v + return v + + class RawMediaItem(BaseModel): type: Literal["image"] = "image" data: bytes | BytesIO @@ -140,292 +154,25 @@ class RawMessage(BaseModel): tool_calls: List[ToolCall] = Field(default_factory=list) -register_schema(ToolCall) +class GenerationResult(BaseModel): + token: int + text: str + logprobs: Optional[List[float]] = None + + source: Literal["input"] | Literal["output"] + + # index within the batch + batch_idx: int + # whether generation for this item is already finished. note that tokens can + # get returned even afterwards since other items in the batch can still be generating tokens + finished: bool + # because a batch is parallel processed, useful decoding for one item can correspond to processing + # pad tokens or tokens beyond EOS for other items. we could have decided to return None for this case + # but it's more convenient to return a list of GenerationResult and filter out the ignored tokens + ignore_token: bool -@json_schema_type -class ToolParamDefinition(BaseModel): - param_type: str - description: Optional[str] = None - required: Optional[bool] = True - default: Optional[Any] = None - - -@json_schema_type -class ToolDefinition(BaseModel): - tool_name: Union[BuiltinTool, str] - description: Optional[str] = None - parameters: Optional[Dict[str, ToolParamDefinition]] = None - - @field_validator("tool_name", mode="before") - @classmethod - def validate_field(cls, v): - if isinstance(v, str): - try: - return BuiltinTool(v) - except ValueError: - return v - return v - - -@json_schema_type -class GreedySamplingStrategy(BaseModel): - type: Literal["greedy"] = "greedy" - - -@json_schema_type -class TopPSamplingStrategy(BaseModel): - type: Literal["top_p"] = "top_p" - temperature: Optional[float] = Field(..., gt=0.0) - top_p: Optional[float] = 0.95 - - -@json_schema_type -class TopKSamplingStrategy(BaseModel): - type: Literal["top_k"] = "top_k" - top_k: int = Field(..., ge=1) - - -SamplingStrategy = Annotated[ - Union[GreedySamplingStrategy, TopPSamplingStrategy, TopKSamplingStrategy], - Field(discriminator="type"), -] -register_schema(SamplingStrategy, name="SamplingStrategy") - - -@json_schema_type -class SamplingParams(BaseModel): - """Sampling parameters. - - :param strategy: The sampling strategy. - :param max_tokens: The maximum number of tokens that can be generated in the completion. The token count of - your prompt plus max_tokens cannot exceed the model's context length. - :param repetition_penalty: Number between -2.0 and 2.0. Positive values penalize new tokens - based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics. - :param stop: Up to 4 sequences where the API will stop generating further tokens. - The returned text will not contain the stop sequence. - """ - - strategy: SamplingStrategy = Field(default_factory=GreedySamplingStrategy) - - max_tokens: Optional[int] = 0 - repetition_penalty: Optional[float] = 1.0 - stop: Optional[List[str]] = None - - -class CheckpointQuantizationFormat(Enum): - # default format - bf16 = "bf16" - - # used for enabling fp8_rowwise inference, some weights are bf16 - fp8_mixed = "fp8-mixed" - - int8 = "int8" - - int4 = "int4" - - -class ModelFamily(Enum): - llama2 = "llama2" - llama3 = "llama3" - llama3_1 = "llama3_1" - llama3_2 = "llama3_2" - llama3_3 = "llama3_3" - llama4 = "llama4" - safety = "safety" - - -class CoreModelId(Enum): - """Each of these models is a unique "SKU". These root models can be served in various garbs (especially by quantizing them)""" - - # Llama 2 family - llama2_7b = "Llama-2-7b" - llama2_13b = "Llama-2-13b" - llama2_70b = "Llama-2-70b" - llama2_7b_chat = "Llama-2-7b-chat" - llama2_13b_chat = "Llama-2-13b-chat" - llama2_70b_chat = "Llama-2-70b-chat" - - # Llama 3 family - llama3_8b = "Llama-3-8B" - llama3_70b = "Llama-3-70B" - llama3_8b_instruct = "Llama-3-8B-Instruct" - llama3_70b_instruct = "Llama-3-70B-Instruct" - - # Llama 3.1 family - llama3_1_8b = "Llama3.1-8B" - llama3_1_70b = "Llama3.1-70B" - llama3_1_405b = "Llama3.1-405B" - llama3_1_8b_instruct = "Llama3.1-8B-Instruct" - llama3_1_70b_instruct = "Llama3.1-70B-Instruct" - llama3_1_405b_instruct = "Llama3.1-405B-Instruct" - - # Llama 3.2 family - llama3_2_1b = "Llama3.2-1B" - llama3_2_3b = "Llama3.2-3B" - llama3_2_1b_instruct = "Llama3.2-1B-Instruct" - llama3_2_3b_instruct = "Llama3.2-3B-Instruct" - llama3_2_11b_vision = "Llama3.2-11B-Vision" - llama3_2_90b_vision = "Llama3.2-90B-Vision" - llama3_2_11b_vision_instruct = "Llama3.2-11B-Vision-Instruct" - llama3_2_90b_vision_instruct = "Llama3.2-90B-Vision-Instruct" - - # Llama 3.3 family - llama3_3_70b_instruct = "Llama3.3-70B-Instruct" - - # Llama 4 family - llama4_scout_17b_16e = "Llama-4-Scout-17B-16E" - llama4_scout_17b_16e_instruct = "Llama-4-Scout-17B-16E-Instruct" - llama4_maverick_17b_128e = "Llama-4-Maverick-17B-128E" - llama4_maverick_17b_128e_instruct = "Llama-4-Maverick-17B-128E-Instruct" - - # Safety models - llama_guard_3_8b = "Llama-Guard-3-8B" - llama_guard_2_8b = "Llama-Guard-2-8B" - llama_guard_3_11b_vision = "Llama-Guard-3-11B-Vision" - llama_guard_3_1b = "Llama-Guard-3-1B" - - -def is_multimodal(model_id) -> bool: - if model_id in [ - CoreModelId.llama3_2_11b_vision, - CoreModelId.llama3_2_90b_vision, - CoreModelId.llama3_2_11b_vision_instruct, - CoreModelId.llama3_2_90b_vision_instruct, - ]: - return True - else: - return False - - -def model_family(model_id) -> ModelFamily: - if model_id in [ - CoreModelId.llama2_7b, - CoreModelId.llama2_13b, - CoreModelId.llama2_70b, - CoreModelId.llama2_7b_chat, - CoreModelId.llama2_13b_chat, - CoreModelId.llama2_70b_chat, - ]: - return ModelFamily.llama2 - elif model_id in [ - CoreModelId.llama3_8b, - CoreModelId.llama3_70b, - CoreModelId.llama3_8b_instruct, - CoreModelId.llama3_70b_instruct, - ]: - return ModelFamily.llama3 - elif model_id in [ - CoreModelId.llama3_1_8b, - CoreModelId.llama3_1_70b, - CoreModelId.llama3_1_405b, - CoreModelId.llama3_1_8b_instruct, - CoreModelId.llama3_1_70b_instruct, - CoreModelId.llama3_1_405b_instruct, - ]: - return ModelFamily.llama3_1 - elif model_id in [ - CoreModelId.llama3_2_1b, - CoreModelId.llama3_2_3b, - CoreModelId.llama3_2_1b_instruct, - CoreModelId.llama3_2_3b_instruct, - CoreModelId.llama3_2_11b_vision, - CoreModelId.llama3_2_90b_vision, - CoreModelId.llama3_2_11b_vision_instruct, - CoreModelId.llama3_2_90b_vision_instruct, - ]: - return ModelFamily.llama3_2 - elif model_id in [ - CoreModelId.llama3_3_70b_instruct, - ]: - return ModelFamily.llama3_3 - elif model_id in [ - CoreModelId.llama4_scout_17b_16e, - CoreModelId.llama4_scout_17b_16e_instruct, - CoreModelId.llama4_maverick_17b_128e, - CoreModelId.llama4_maverick_17b_128e_instruct, - ]: - return ModelFamily.llama4 - elif model_id in [ - CoreModelId.llama_guard_3_8b, - CoreModelId.llama_guard_2_8b, - CoreModelId.llama_guard_3_11b_vision, - CoreModelId.llama_guard_3_1b, - ]: - return ModelFamily.safety - else: - raise ValueError(f"Unknown model family for {model_id}") - - -class Model(BaseModel): - core_model_id: CoreModelId - description: str - huggingface_repo: Optional[str] = None - recommended_sampling_params: Optional[SamplingParams] = None - arch_args: Dict[str, Any] - variant: str = "" - - quantization_format: CheckpointQuantizationFormat = CheckpointQuantizationFormat.bf16 - pth_file_count: int - metadata: Optional[Dict[str, Any]] = Field(default_factory=dict) - - # silence pydantic until we remove the `model_` fields - model_config = ConfigDict(protected_namespaces=()) - - @property - def model_family(self) -> ModelFamily: - return model_family(self.core_model_id) - - # The SKU is uniquely identified by (model_id, variant) combo - def descriptor(self, shorten_default_variant: bool = True) -> str: - if not self.variant: - return self.core_model_id.value - return f"{self.core_model_id.value}:{self.variant}" - - @property - def is_instruct_model(self) -> bool: - return "instruct" in self.id.name - - # Featured models are shown in the non-exhaustive model list - @property - def is_featured(self) -> bool: - return self.model_family in [ - ModelFamily.llama3_1, - ModelFamily.llama3_2, - ModelFamily.llama3_3, - ModelFamily.llama4, - ModelFamily.safety, - ] - - @property - def max_seq_length(self) -> int: - if self.model_family == ModelFamily.llama2: - return 4096 - elif self.core_model_id == CoreModelId.llama_guard_2_8b: - return 4096 - elif self.model_family == ModelFamily.llama3: - return 8192 - elif self.model_family in [ModelFamily.llama3_1, ModelFamily.llama3_3]: - return 131072 - elif self.model_family == ModelFamily.llama3_2: - if self.quantization_format == CheckpointQuantizationFormat.int4: - return 8192 - return 131072 - elif self.model_family == ModelFamily.llama4: - if self.core_model_id in { - CoreModelId.llama4_scout_17b_16e, - CoreModelId.llama4_maverick_17b_128e, - }: - return 262144 - if self.core_model_id == CoreModelId.llama4_scout_17b_16e_instruct: - return 10485760 - if self.core_model_id == CoreModelId.llama4_maverick_17b_128e_instruct: - return 1048576 - elif self.core_model_id in [ - CoreModelId.llama_guard_3_8b, - CoreModelId.llama_guard_3_11b_vision, - CoreModelId.llama_guard_3_1b, - ]: - return 131072 - else: - raise ValueError(f"Unknown max_seq_len for {self.core_model_id}") +class QuantizationMode(str, Enum): + none = "none" + fp8_mixed = "fp8_mixed" + int4_mixed = "int4_mixed" diff --git a/llama_stack/providers/inline/inference/meta_reference/hadamard_utils.py b/llama_stack/models/llama/hadamard_utils.py similarity index 100% rename from llama_stack/providers/inline/inference/meta_reference/hadamard_utils.py rename to llama_stack/models/llama/hadamard_utils.py diff --git a/llama_stack/providers/inline/inference/meta_reference/llama3/args.py b/llama_stack/models/llama/llama3/args.py similarity index 88% rename from llama_stack/providers/inline/inference/meta_reference/llama3/args.py rename to llama_stack/models/llama/llama3/args.py index e96eaca61..f7e4b4557 100644 --- a/llama_stack/providers/inline/inference/meta_reference/llama3/args.py +++ b/llama_stack/models/llama/llama3/args.py @@ -4,13 +4,6 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the terms described in the LICENSE file in -# top-level folder for each specific model found within the models/ directory at -# the top-level of this source tree. - from dataclasses import dataclass from enum import Enum from typing import Optional diff --git a/llama_stack/models/llama/llama3/chat_format.py b/llama_stack/models/llama/llama3/chat_format.py index 2862f8558..f55cd5e1c 100644 --- a/llama_stack/models/llama/llama3/chat_format.py +++ b/llama_stack/models/llama/llama3/chat_format.py @@ -4,13 +4,6 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the terms described in the LICENSE file in -# top-level folder for each specific model found within the models/ directory at -# the top-level of this source tree. - import io import json import uuid @@ -19,7 +12,7 @@ from typing import Dict, List, Optional, Tuple from PIL import Image as PIL_Image -from llama_stack.models.llama.datatypes import ( +from ..datatypes import ( BuiltinTool, RawContent, RawMediaItem, @@ -30,7 +23,6 @@ from llama_stack.models.llama.datatypes import ( ToolCall, ToolPromptFormat, ) - from .tokenizer import Tokenizer from .tool_utils import ToolUtils diff --git a/llama_stack/models/llama/llama3/generation.py b/llama_stack/models/llama/llama3/generation.py new file mode 100644 index 000000000..ee99a07ba --- /dev/null +++ b/llama_stack/models/llama/llama3/generation.py @@ -0,0 +1,367 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# top-level folder for each specific model found within the models/ directory at +# the top-level of this source tree. + +import json +import os +import sys +import time +from pathlib import Path +from typing import Callable, Generator, List, Optional + +import torch +import torch.nn.functional as F +from fairscale.nn.model_parallel.initialize import ( + initialize_model_parallel, + model_parallel_is_initialized, +) +from termcolor import cprint + +from ..checkpoint import maybe_reshard_state_dict +from ..datatypes import GenerationResult, QuantizationMode, RawContent, RawMessage, ToolPromptFormat +from .args import ModelArgs +from .chat_format import ChatFormat, LLMInput +from .model import Transformer +from .multimodal.model import CrossAttentionTransformer +from .tokenizer import Tokenizer + + +class Llama3: + @staticmethod + def build( + ckpt_dir: str, + max_seq_len: int, + max_batch_size: int, + world_size: Optional[int] = None, + quantization_mode: Optional[QuantizationMode] = None, + seed: int = 1, + device: str = "cuda", + ): + device = torch.device(device) + if ( + device.type == "cuda" + and not torch.cuda.is_available() + or device.type == "xpu" + and not torch.xpu.is_available() + ): + raise RuntimeError(f"PyTorch backend for {device.type} device type is not available") + + if not torch.distributed.is_initialized(): + if device.type == "cuda": + torch.distributed.init_process_group("nccl") + else: + torch.distributed.init_process_group("gloo") + + if not model_parallel_is_initialized(): + if world_size is None: + world_size = int(os.environ.get("WORLD_SIZE", 1)) + initialize_model_parallel(world_size) + + local_rank = int(os.environ.get("LOCAL_RANK", 0)) + if device.type == "cuda": + torch.cuda.set_device(local_rank) + elif device.type == "xpu": + torch.xpu.set_device(local_rank) + + torch.manual_seed(seed) + + if local_rank > 0: + sys.stdout = open(os.devnull, "w") + + start_time = time.time() + + ckpt_paths = sorted(Path(ckpt_dir).glob("*.pth")) + assert len(ckpt_paths) > 0, f"no checkpoint files found in {ckpt_dir}" + print(f"Loading a checkpoint (shards={len(ckpt_paths)}, current-mp-size={world_size})") + with open(Path(ckpt_dir) / "params.json", "r") as f: + params = json.loads(f.read()) + + model_args: ModelArgs = ModelArgs( + max_seq_len=max_seq_len, + max_batch_size=max_batch_size, + **params, + ) + tokenizer = Tokenizer.get_instance() + + state_dict = maybe_reshard_state_dict( + ckpt_paths, + n_kv_heads=model_args.n_kv_heads if model_args.n_kv_heads else model_args.n_heads, + ) + + assert model_args.vocab_size == tokenizer.n_words + + def build_model(): + if model_args.vision_chunk_size > 0: + model = CrossAttentionTransformer(model_args) + model.setup_cache(model_args.max_batch_size, device=device, dtype=torch.get_default_dtype()) + else: + model = Transformer(model_args) + return model + + if quantization_mode == QuantizationMode.fp8_mixed or quantization_mode == QuantizationMode.int4_mixed: + from .quantization.loader import convert_to_quantized_model + + torch.set_default_tensor_type(torch.BFloat16Tensor) + model = build_model() + print("Loading state dict...") + model.load_state_dict(state_dict, strict=False) + print("Done...") + model = convert_to_quantized_model(model, ckpt_dir, quantization_mode, device=device) + torch.set_default_device(device) + else: + print(f"Setting default device to {device}") + torch.set_default_device(device) + if device.type == "cuda": + if torch.cuda.is_bf16_supported(): + torch.set_default_dtype(torch.bfloat16) + else: + torch.set_default_dtype(torch.half) + elif device.type == "xpu": + if torch.xpu.is_bf16_supported(): + torch.set_default_dtype(torch.bfloat16) + else: + torch.set_default_dtype(torch.half) + + model = build_model() + print("Loading state dict...") + model.load_state_dict(state_dict, strict=True) + model.to(device) + print("Done...") + + print(f"Loaded in {time.time() - start_time:.2f} seconds") + + return Llama3(model, tokenizer, model_args) + + def __init__(self, model: Transformer | CrossAttentionTransformer, tokenizer: Tokenizer, args: ModelArgs): + self.args = args + self.model = model + self.tokenizer = tokenizer + self.formatter = ChatFormat(tokenizer) + + @torch.inference_mode() + def generate( + self, + model_inputs: List[LLMInput], + temperature: float = 0.6, + top_p: float = 0.9, + max_gen_len: Optional[int] = None, + logprobs: bool = False, + echo: bool = False, + print_model_input: bool = False, + logits_processor: Optional[Callable[[torch.Tensor, torch.Tensor], torch.Tensor]] = None, + ) -> Generator[List[GenerationResult], None, None]: + if max_gen_len is None or max_gen_len == 0 or max_gen_len >= self.args.max_seq_len: + max_gen_len = self.args.max_seq_len - 1 + params = self.model.params + + print_model_input = print_model_input or os.environ.get("LLAMA_MODELS_DEBUG", "0") == "1" + if print_model_input: + for inp in model_inputs: + tokens_to_print = [self.formatter.vision_token if t == 128256 else t for t in inp.tokens] + cprint( + "Input to model:\n" + self.tokenizer.decode(tokens_to_print) + "\n", + "red", + ) + prompt_tokens = [inp.tokens for inp in model_inputs] + + bsz = len(model_inputs) + assert bsz <= params.max_batch_size, (bsz, params.max_batch_size) + + min_prompt_len = min(len(t) for t in prompt_tokens) + max_prompt_len = max(len(t) for t in prompt_tokens) + + if max_prompt_len >= params.max_seq_len: + cprint(f"Out of token budget {max_prompt_len} vs {params.max_seq_len}", "red") + return + + total_len = min(max_gen_len + max_prompt_len, params.max_seq_len) + + pad_id = self.tokenizer.pad_id + tokens = torch.full((bsz, total_len), pad_id, dtype=torch.long) + for k, t in enumerate(prompt_tokens): + tokens[k, : len(t)] = torch.tensor(t, dtype=torch.long) + if logprobs: + token_logprobs = torch.zeros_like(tokens, dtype=torch.float) + + is_vision = not isinstance(self.model, Transformer) + if is_vision: + images = [inp.vision.images if inp.vision is not None else [] for inp in model_inputs] + mask = [inp.vision.mask if inp.vision is not None else [] for inp in model_inputs] + + xattn_caches, cross_attention_masks, full_text_row_masked_out_mask = self.model.compute_vision_tokens_masks( + batch_images=images, + batch_masks=mask, + total_len=total_len, + device=tokens.device, + ) + + eos_reached = torch.tensor([False] * bsz) + input_text_mask = tokens != pad_id + + if echo: + for i in range(max_prompt_len): + results = [] + for j, t in enumerate(tokens[:, i]): + results.append( + GenerationResult( + token=t.item(), + text=self.tokenizer.decode([t.item()]), + source="input", + logprobs=(token_logprobs[j, i : i + 1].tolist() if logprobs else None), + batch_idx=j, + finished=False, + ignore_token=t.item() == pad_id, + ) + ) + yield results + + stop_tokens = torch.tensor(self.tokenizer.stop_tokens) + + prev_pos = 0 + for cur_pos in range(min_prompt_len, total_len): + if is_vision: + position_ids = torch.arange(prev_pos, cur_pos, dtype=torch.long) + text_only_inference = all(inp.vision is None for inp in model_inputs) + logits = self.model.forward( + position_ids, + tokens, + cross_attention_masks, + full_text_row_masked_out_mask, + xattn_caches, + text_only_inference, + ) + else: + logits = self.model.forward(tokens[:, prev_pos:cur_pos], prev_pos) + + if logits_processor is not None: + logits = logits_processor(tokens[:, :cur_pos], logits) + + if temperature > 0: + probs = torch.softmax(logits[:, -1] / temperature, dim=-1) + next_token = sample_top_p(probs, top_p) + else: + next_token = torch.argmax(logits[:, -1], dim=-1) + + next_token = next_token.reshape(-1) + # only replace token if prompt has already been generated + next_token = torch.where(input_text_mask[:, cur_pos], tokens[:, cur_pos], next_token) + tokens[:, cur_pos] = next_token + + target = tokens[:, prev_pos + 1 : cur_pos + 1] + if is_vision: + # the logits space (num_classes) is designed to never contain a media_token + # however our input token stream does contain them. we need to nuke them here + # or else the CUDA kernels will crash with an illegal memory access + vision_tokens = [self.tokenizer.special_tokens["<|image|>"], 128256] + masks = [target.eq(t) for t in vision_tokens] + if len(masks) > 1: + mask = torch.logical_or(*masks) + else: + mask = masks[0] + target[mask] = 0 + + if logprobs: + token_logprobs[:, prev_pos + 1 : cur_pos + 1] = -F.cross_entropy( + input=logits.transpose(1, 2), + target=target, + reduction="none", + ignore_index=pad_id, + ) + eos_reached |= (~input_text_mask[:, cur_pos]) & (torch.isin(next_token, stop_tokens)) + results = [] + for idx, t in enumerate(next_token): + results.append( + GenerationResult( + token=t.item(), + text=self.tokenizer.decode([t.item()]), + source="output", + logprobs=(token_logprobs[idx, cur_pos : cur_pos + 1].tolist() if logprobs else None), + batch_idx=idx, + finished=eos_reached[idx], + ignore_token=cur_pos < len(prompt_tokens[idx]), + ) + ) + yield results + + prev_pos = cur_pos + if all(eos_reached): + break + + def completion( + self, + contents: List[RawContent], + temperature: float = 0.6, + top_p: float = 0.9, + max_gen_len: Optional[int] = None, + logprobs: bool = False, + echo: bool = False, + ) -> Generator[List[GenerationResult], None, None]: + model_inputs = [self.formatter.encode_content(c) for c in contents] + for result in self.generate( + model_inputs=model_inputs, + temperature=temperature, + top_p=top_p, + max_gen_len=max_gen_len, + logprobs=logprobs, + echo=echo, + ): + yield result + if all(r.finished for r in result): + break + + def chat_completion( + self, + messages_batch: List[List[RawMessage]], + temperature: float = 0.6, + top_p: float = 0.9, + max_gen_len: Optional[int] = None, + logprobs: bool = False, + tool_prompt_format: ToolPromptFormat = ToolPromptFormat.json, + echo: bool = False, + ) -> Generator[List[GenerationResult], None, None]: + model_inputs = [self.formatter.encode_dialog_prompt(messages) for messages in messages_batch] + for result in self.generate( + model_inputs=model_inputs, + temperature=temperature, + top_p=top_p, + max_gen_len=max_gen_len, + logprobs=logprobs, + echo=echo, + ): + yield result + if all(r.finished for r in result): + break + + +def sample_top_p(probs, p): + """ + Perform top-p (nucleus) sampling on a probability distribution. + + Args: + probs (torch.Tensor): Probability distribution tensor. + p (float): Probability threshold for top-p sampling. + + Returns: + torch.Tensor: Sampled token indices. + + Note: + Top-p sampling selects the smallest set of tokens whose cumulative probability mass + exceeds the threshold p. The distribution is renormalized based on the selected tokens. + """ + probs_sort, probs_idx = torch.sort(probs, dim=-1, descending=True) + probs_sum = torch.cumsum(probs_sort, dim=-1) + mask = probs_sum - probs_sort > p + probs_sort[mask] = 0.0 + probs_sort.div_(probs_sort.sum(dim=-1, keepdim=True)) + next_token = torch.multinomial(probs_sort, num_samples=1) + next_token = torch.gather(probs_idx, -1, next_token) + return next_token diff --git a/llama_stack/models/llama/llama3/interface.py b/llama_stack/models/llama/llama3/interface.py index 2579ab6c8..8684237df 100644 --- a/llama_stack/models/llama/llama3/interface.py +++ b/llama_stack/models/llama/llama3/interface.py @@ -16,7 +16,7 @@ from typing import List, Optional from termcolor import colored -from llama_stack.models.llama.datatypes import ( +from ..datatypes import ( BuiltinTool, RawMessage, StopReason, @@ -24,7 +24,6 @@ from llama_stack.models.llama.datatypes import ( ToolDefinition, ToolPromptFormat, ) - from . import template_data from .chat_format import ChatFormat from .prompt_templates import ( diff --git a/llama_stack/providers/inline/inference/meta_reference/llama3/model.py b/llama_stack/models/llama/llama3/model.py similarity index 94% rename from llama_stack/providers/inline/inference/meta_reference/llama3/model.py rename to llama_stack/models/llama/llama3/model.py index a49167980..2562673e2 100644 --- a/llama_stack/providers/inline/inference/meta_reference/llama3/model.py +++ b/llama_stack/models/llama/llama3/model.py @@ -4,16 +4,6 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the terms described in the LICENSE file in -# top-level folder for each specific model found within the models/ directory at -# the top-level of this source tree. - -# Copyright (c) Meta Platforms, Inc. and affiliates. -# This software may be used and distributed in accordance with the terms of the Llama 3 Community License Agreement. - import math from typing import Optional, Tuple @@ -29,6 +19,10 @@ from torch import nn from .args import ModelArgs +# **NOTE**: This code is not runnable without installing `torch` and `fairscale` +# dependencies. These dependencies are not part of the default dependencies +# (requirements.txt) of the `llama-models` package. + class RMSNorm(torch.nn.Module): def __init__(self, dim: int, eps: float = 1e-6): @@ -111,9 +105,9 @@ class Attention(nn.Module): def __init__(self, args: ModelArgs): super().__init__() self.n_kv_heads = args.n_heads if args.n_kv_heads is None else args.n_kv_heads - model_parallel_size = fs_init.get_model_parallel_world_size() - self.n_local_heads = args.n_heads // model_parallel_size - self.n_local_kv_heads = self.n_kv_heads // model_parallel_size + world_size = fs_init.get_model_parallel_world_size() + self.n_local_heads = args.n_heads // world_size + self.n_local_kv_heads = self.n_kv_heads // world_size self.n_rep = self.n_local_heads // self.n_local_kv_heads self.head_dim = args.dim // args.n_heads diff --git a/llama_stack/providers/inline/inference/meta_reference/llama3/multimodal/__init__.py b/llama_stack/models/llama/llama3/multimodal/__init__.py similarity index 100% rename from llama_stack/providers/inline/inference/meta_reference/llama3/multimodal/__init__.py rename to llama_stack/models/llama/llama3/multimodal/__init__.py diff --git a/llama_stack/providers/inline/inference/meta_reference/llama3/multimodal/encoder_utils.py b/llama_stack/models/llama/llama3/multimodal/encoder_utils.py similarity index 100% rename from llama_stack/providers/inline/inference/meta_reference/llama3/multimodal/encoder_utils.py rename to llama_stack/models/llama/llama3/multimodal/encoder_utils.py diff --git a/llama_stack/providers/inline/inference/meta_reference/llama3/multimodal/image_transform.py b/llama_stack/models/llama/llama3/multimodal/image_transform.py similarity index 100% rename from llama_stack/providers/inline/inference/meta_reference/llama3/multimodal/image_transform.py rename to llama_stack/models/llama/llama3/multimodal/image_transform.py diff --git a/llama_stack/providers/inline/inference/meta_reference/llama3/multimodal/model.py b/llama_stack/models/llama/llama3/multimodal/model.py similarity index 95% rename from llama_stack/providers/inline/inference/meta_reference/llama3/multimodal/model.py rename to llama_stack/models/llama/llama3/multimodal/model.py index 3d0d77c87..0cb18b948 100644 --- a/llama_stack/providers/inline/inference/meta_reference/llama3/multimodal/model.py +++ b/llama_stack/models/llama/llama3/multimodal/model.py @@ -4,16 +4,6 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the terms described in the LICENSE file in -# top-level folder for each specific model found within the models/ directory at -# the top-level of this source tree. - -# Copyright (c) Meta Platforms, Inc. and affiliates. -# This software may be used and distributed according to the terms of the Llama 2 Community License Agreement. - import logging import math from functools import partial @@ -180,14 +170,14 @@ class ImageAttention(nn.Module): n_heads, ): super().__init__() - model_parallel_size = fs_init.get_model_parallel_world_size() + world_size = fs_init.get_model_parallel_world_size() qkvo_replication = 1 - if model_parallel_size > 16: - qkvo_replication = model_parallel_size // 8 + if world_size > 16: + qkvo_replication = world_size // 8 self.n_kv_heads = n_heads - self.n_local_heads = n_heads * qkvo_replication // model_parallel_size - self.n_local_kv_heads = self.n_kv_heads * qkvo_replication // model_parallel_size + self.n_local_heads = n_heads * qkvo_replication // world_size + self.n_local_kv_heads = self.n_kv_heads * qkvo_replication // world_size self.n_rep = self.n_local_heads // self.n_local_kv_heads self.head_dim = dim // n_heads @@ -536,16 +526,16 @@ class Attention(nn.Module): cache_v (torch.Tensor): Cached values for attention. """ super().__init__() - model_parallel_size = fs_init.get_model_parallel_world_size() + world_size = fs_init.get_model_parallel_world_size() replication_factor = 1 - if model_parallel_size > 8: - replication_factor = model_parallel_size // MP_SCALE + if world_size > 8: + replication_factor = world_size // MP_SCALE self.n_kv_heads = args.n_heads if args.n_kv_heads is None else args.n_kv_heads self.n_kv_heads *= replication_factor - self.n_local_heads = args.n_heads // model_parallel_size - self.n_local_kv_heads = self.n_kv_heads // model_parallel_size + self.n_local_heads = args.n_heads // world_size + self.n_local_kv_heads = self.n_kv_heads // world_size self.n_rep = self.n_local_heads // self.n_local_kv_heads self.head_dim = args.dim // args.n_heads self.max_seq_len = args.max_seq_len @@ -587,13 +577,11 @@ class Attention(nn.Module): self.n_local_kv_heads, self.head_dim, ) - device = next(self.parameters()).device self.register_buffer( "key_cache", torch.zeros( cache_shape, dtype=dtype, - device=device, ), persistent=False, ) @@ -602,7 +590,6 @@ class Attention(nn.Module): torch.zeros( cache_shape, dtype=dtype, - device=device, ), persistent=False, ) @@ -614,6 +601,9 @@ class Attention(nn.Module): freqs_cis: torch.Tensor, position_ids: torch.LongTensor, ): + self.key_cache = self.key_cache.to(x.device) + self.value_cache = self.value_cache.to(x.device) + xq, xk, xv = [F.linear(x, w) for w in [self.wq.weight, self.wk.weight, self.wv.weight]] bs, slen, _ = xq.shape @@ -832,10 +822,10 @@ class CrossAttention(torch.nn.Module): norm_eps: float, ): super().__init__() - self.model_parallel_size = fs_init.get_model_parallel_world_size() + self.world_size = fs_init.get_model_parallel_world_size() replication_factor = 1 - if self.model_parallel_size > 8: - replication_factor = self.model_parallel_size // MP_SCALE + if self.world_size > 8: + replication_factor = self.world_size // MP_SCALE n_kv_heads *= replication_factor assert n_heads % n_kv_heads == 0 @@ -889,10 +879,10 @@ class CrossAttention(torch.nn.Module): # trunk LLM (i.e., group query attention) -- @dubeya # local heads assert self.n_heads % self.n_kv_heads == 0 - assert self.n_heads % self.model_parallel_size == 0 - assert self.n_kv_heads % self.model_parallel_size == 0 - self.n_local_heads = self.n_heads // self.model_parallel_size - self.n_local_kv_heads = self.n_kv_heads // self.model_parallel_size + assert self.n_heads % self.world_size == 0 + assert self.n_kv_heads % self.world_size == 0 + self.n_local_heads = self.n_heads // self.world_size + self.n_local_kv_heads = self.n_kv_heads // self.world_size self.n_rep = self.n_local_heads // self.n_local_kv_heads def _compute_xattn_kv_cache(self, xattn_tokens: torch.Tensor) -> torch.Tensor: @@ -1041,7 +1031,7 @@ class CrossAttentionTransformerVision(torch.nn.Module): self.image_res = args.vision_chunk_size self.max_num_chunks = args.vision_max_num_chunks if return_intermediate is not None: - return_intermediate = [int(level) for level in return_intermediate.split(",")] + return_intermediate = [int(layer) for layer in return_intermediate.split(",")] self.vision_input_dim = (len(return_intermediate) + 1) * self.vision_input_dim self.patch_size = 14 self.vision_encoder = VisionEncoder( @@ -1076,15 +1066,15 @@ class CrossAttentionTransformerText(torch.nn.Module): def __init__(self, args: ModelArgs) -> None: super().__init__() - self.model_parallel_size = fs_init.get_model_parallel_world_size() + self.world_size = fs_init.get_model_parallel_world_size() assert args.vocab_size > 0 self.vocab_size = args.vocab_size self.n_layers = args.n_layers self.dim = args.dim self.head_dim = args.dim // args.n_heads self.n_kv_heads = args.n_heads if args.n_kv_heads is None else args.n_kv_heads - self.n_local_kv_heads = self.n_kv_heads // self.model_parallel_size - assert self.vocab_size % self.model_parallel_size == 0 + self.n_local_kv_heads = self.n_kv_heads // self.world_size + assert self.vocab_size % self.world_size == 0 self.tok_embeddings = VocabParallelEmbedding(args.vocab_size, args.dim, init_method=lambda x: x) self.pos_embeddings = None # final norm layer (not necessary for post-norm) @@ -1184,6 +1174,8 @@ class CrossAttentionTransformerText(torch.nn.Module): text_only_inference: bool = False, ): assert self.cache_is_setup, "Please set up cache before calling forward" + self.mask_cache = self.mask_cache.to(h.device) + self.freqs_cis = self.freqs_cis.to(h.device) mask = self.mask_cache.index_select(2, position_ids) freqs_cis = self.freqs_cis.index_select(0, position_ids) @@ -1212,9 +1204,8 @@ class CrossAttentionTransformerText(torch.nn.Module): output = gather_from_tensor_model_parallel_region(output) return output.float() - def setup_cache(self, max_batch_size: int, dtype=torch.bfloat16): + def setup_cache(self, max_batch_size: int, device: torch.device, dtype=torch.bfloat16): # Set up the text kv caches - device = next(self.parameters()).device ones = torch.ones( (self.max_seq_len, self.max_seq_len), dtype=torch.bool, @@ -1265,7 +1256,7 @@ class CrossAttentionTransformerText(torch.nn.Module): return ( cross_attention_masks.to(device=text_device, dtype=text_dtype), - full_text_row_masked_out_mask, + full_text_row_masked_out_mask.to(device=text_device), ) @@ -1284,14 +1275,15 @@ class CrossAttentionTransformer(torch.nn.Module): max_num_chunks=args.vision_max_num_chunks, ) - def setup_cache(self, max_batch_size: int, dtype: torch.dtype): - self.text_model.setup_cache(max_batch_size, dtype) + def setup_cache(self, max_batch_size: int, device: torch.device, dtype: torch.dtype): + self.text_model.setup_cache(max_batch_size, device, dtype) def compute_vision_tokens_masks( self, batch_images: List[List[PIL_Image.Image]], batch_masks: List[List[List[int]]], total_len: int, + device: torch.device, ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: skip_vision_encoder = False @@ -1318,6 +1310,7 @@ class CrossAttentionTransformer(torch.nn.Module): image_res=self.params.vision_chunk_size, max_num_images=max_num_images, ) + stacked_images = stacked_images.to(device=device) if skip_vision_encoder: vision_tokens = torch.zeros( @@ -1330,7 +1323,7 @@ class CrossAttentionTransformer(torch.nn.Module): ), ) else: - vision_tokens = self.vision_model(stacked_images, aspect_ratios) + vision_tokens = self.vision_model(stacked_images, aspect_ratios).to(device=device) bsz, nimg, nchunk, ntok, image_token_dim = tuple(vision_tokens.shape) xattn_caches = torch.stack( diff --git a/llama_stack/providers/inline/inference/meta_reference/llama3/multimodal/utils.py b/llama_stack/models/llama/llama3/multimodal/utils.py similarity index 100% rename from llama_stack/providers/inline/inference/meta_reference/llama3/multimodal/utils.py rename to llama_stack/models/llama/llama3/multimodal/utils.py diff --git a/llama_stack/models/llama/llama3/prompt_templates/system_prompts.py b/llama_stack/models/llama/llama3/prompt_templates/system_prompts.py index e03fcfc93..d4e825a22 100644 --- a/llama_stack/models/llama/llama3/prompt_templates/system_prompts.py +++ b/llama_stack/models/llama/llama3/prompt_templates/system_prompts.py @@ -15,7 +15,7 @@ import textwrap from datetime import datetime from typing import Any, List, Optional -from llama_stack.models.llama.datatypes import ( +from llama_stack.apis.inference import ( BuiltinTool, ToolDefinition, ToolParamDefinition, diff --git a/llama_stack/templates/meta-reference-quantized-gpu/__init__.py b/llama_stack/models/llama/llama3/quantization/__init__.py similarity index 74% rename from llama_stack/templates/meta-reference-quantized-gpu/__init__.py rename to llama_stack/models/llama/llama3/quantization/__init__.py index 1cfdb2c6a..756f351d8 100644 --- a/llama_stack/templates/meta-reference-quantized-gpu/__init__.py +++ b/llama_stack/models/llama/llama3/quantization/__init__.py @@ -3,5 +3,3 @@ # # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. - -from .meta_reference import get_distribution_template # noqa: F401 diff --git a/llama_stack/providers/inline/inference/meta_reference/llama3/quantization/loader.py b/llama_stack/models/llama/llama3/quantization/loader.py similarity index 84% rename from llama_stack/providers/inline/inference/meta_reference/llama3/quantization/loader.py rename to llama_stack/models/llama/llama3/quantization/loader.py index 5109130b4..771fd02be 100644 --- a/llama_stack/providers/inline/inference/meta_reference/llama3/quantization/loader.py +++ b/llama_stack/models/llama/llama3/quantization/loader.py @@ -4,9 +4,6 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -# Copyright (c) Meta Platforms, Inc. and affiliates. -# This software may be used and distributed in accordance with the terms of the Llama 3 Community License Agreement. - # type: ignore import os from typing import Any, Dict, List, Optional, cast @@ -18,22 +15,15 @@ from fairscale.nn.model_parallel.mappings import reduce_from_model_parallel_regi from torch import Tensor, nn from torchao.quantization.GPTQ import Int8DynActInt4WeightLinear -from llama_stack.apis.inference import QuantizationType -from llama_stack.log import get_logger -from llama_stack.models.llama.datatypes import CheckpointQuantizationFormat -from llama_stack.models.llama.sku_list import resolve_model -from llama_stack.providers.inline.inference.meta_reference.quantize_impls import ( +from ...datatypes import QuantizationMode +from ...quantize_impls import ( Fp8ScaledWeights, ffn_swiglu, load_fp8, quantize_fp8, ) - -from ...config import MetaReferenceQuantizedInferenceConfig -from ..args import ModelArgs from ..model import Transformer, TransformerBlock - -log = get_logger(__name__, category="quantization") +from ..multimodal.model import CrossAttentionTransformer def swiglu_wrapper( @@ -44,30 +34,34 @@ def swiglu_wrapper( return reduce_from_model_parallel_region(out) +def convert_to_quantized_model( + model: Transformer | CrossAttentionTransformer, + checkpoint_dir: str, + quantization_mode: Optional[str] = None, + fp8_activation_scale_ub: Optional[float] = 1200.0, + device: Optional[torch.device] = None, +) -> Transformer | CrossAttentionTransformer: + if quantization_mode == QuantizationMode.fp8_mixed: + return convert_to_fp8_quantized_model(model, checkpoint_dir, fp8_activation_scale_ub, device) + elif quantization_mode == QuantizationMode.int4_mixed: + return convert_to_int4_quantized_model(model, checkpoint_dir, device) + else: + raise ValueError(f"Unsupported quantization mode: {quantization_mode}") + + def convert_to_fp8_quantized_model( model: Transformer, - config: MetaReferenceQuantizedInferenceConfig, checkpoint_dir: str, fp8_activation_scale_ub: Optional[float] = 1200.0, + device: Optional[torch.device] = None, ) -> Transformer: - if config.quantization.type == QuantizationType.bf16.value: - return model - - elif config.quantization.type != QuantizationType.fp8.value: - raise ValueError("Only FP8 quantization is supported") - - assert config.model is not None, "Model must be specified for quantized inference" - llama_model = resolve_model(config.model) - assert llama_model is not None, f"Model {config.model} not found" - # Move weights to GPU with quantization - if llama_model.quantization_format == CheckpointQuantizationFormat.fp8_mixed.value: - log.info("Loading fp8 scales...") - fp8_scales_path = os.path.join(checkpoint_dir, f"fp8_scales_{get_model_parallel_rank()}.pt") - assert os.path.isfile(fp8_scales_path), f"fp8_scales_path not found for rank {get_model_parallel_rank()}" + fp8_scales_path = os.path.join(checkpoint_dir, f"fp8_scales_{get_model_parallel_rank()}.pt") + if os.path.isfile(fp8_scales_path): + print("Loading fp8 scales...") fp8_scales = torch.load(fp8_scales_path, weights_only=True) - for block in model.layers: + for _, block in model.named_modules(): if isinstance(block, TransformerBlock): if block.layer_id == 0 or block.layer_id == (model.n_layers - 1): continue @@ -81,8 +75,8 @@ def convert_to_fp8_quantized_model( fp8_activation_scale_ub, ) else: - log.info("Quantizing fp8 weights from bf16...") - for block in model.layers: + print("Quantizing fp8 weights from bf16...") + for _, block in model.named_modules(): if isinstance(block, TransformerBlock): if block.layer_id == 0 or block.layer_id == (model.n_layers - 1): continue @@ -92,12 +86,12 @@ def convert_to_fp8_quantized_model( param.weight = quantize_fp8( param.weight, fp8_activation_scale_ub, - output_device=torch.device("cuda"), + output_device=device, ) for _, parameter in model.named_parameters(): if not isinstance(parameter, Fp8ScaledWeights): - parameter.data = parameter.to(device="cuda") + parameter.data = parameter.to(device=device) return model @@ -290,12 +284,12 @@ def _prepare_model_int4_weight_int8_dynamic_activation( def convert_to_int4_quantized_model( - model: Transformer, - model_args: ModelArgs, - config: MetaReferenceQuantizedInferenceConfig, -) -> Transformer: + model: Transformer | CrossAttentionTransformer, + checkpoint_dir: str, + device: Optional[torch.device] = None, +) -> Transformer | CrossAttentionTransformer: """Convert the model to int4 quantized model.""" - + model_args = model.params assert model_args.quantization_args is not None, "Quantization args must be specified." quantization_args = model_args.quantization_args if quantization_args.scheme is None: @@ -319,5 +313,4 @@ def convert_to_int4_quantized_model( lora_scale = model_args.lora_args.scale _prepare_model_int4_weight_int8_dynamic_activation(model, group_size, lora_rank, lora_scale) - device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") - return cast(Transformer, model.to(device)) + return cast(Transformer | CrossAttentionTransformer, model.to(device=device)) diff --git a/llama_stack/models/llama/llama3/template_data.py b/llama_stack/models/llama/llama3/template_data.py index 076b4adb4..efca8397e 100644 --- a/llama_stack/models/llama/llama3/template_data.py +++ b/llama_stack/models/llama/llama3/template_data.py @@ -12,8 +12,7 @@ # the top-level of this source tree. -from llama_stack.models.llama.datatypes import BuiltinTool, StopReason, ToolCall - +from ..datatypes import BuiltinTool, StopReason, ToolCall from .prompt_templates import ( BuiltinToolGenerator, JsonCustomToolGenerator, diff --git a/llama_stack/models/llama/llama3/tokenizer.py b/llama_stack/models/llama/llama3/tokenizer.py index b240fa246..d3cc4fc07 100644 --- a/llama_stack/models/llama/llama3/tokenizer.py +++ b/llama_stack/models/llama/llama3/tokenizer.py @@ -4,16 +4,6 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the terms described in the LICENSE file in -# top-level folder for each specific model found within the models/ directory at -# the top-level of this source tree. - -# Copyright (c) Meta Platforms, Inc. and affiliates. -# This software may be used and distributed in accordance with the terms of the Llama 3 Community License Agreement. - import os from logging import getLogger from pathlib import Path diff --git a/llama_stack/models/llama/llama3/tool_utils.py b/llama_stack/models/llama/llama3/tool_utils.py index 71018898c..fc8287eb6 100644 --- a/llama_stack/models/llama/llama3/tool_utils.py +++ b/llama_stack/models/llama/llama3/tool_utils.py @@ -16,7 +16,8 @@ import re from typing import Optional, Tuple from llama_stack.log import get_logger -from llama_stack.models.llama.datatypes import BuiltinTool, RecursiveType, ToolCall, ToolPromptFormat + +from ..datatypes import BuiltinTool, RecursiveType, ToolCall, ToolPromptFormat logger = get_logger(name=__name__, category="inference") diff --git a/llama_stack/models/llama/llama3_2/__init__.py b/llama_stack/models/llama/llama3_2/__init__.py index 38ee47d66..756f351d8 100644 --- a/llama_stack/models/llama/llama3_2/__init__.py +++ b/llama_stack/models/llama/llama3_2/__init__.py @@ -3,10 +3,3 @@ # # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. - -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the terms described in the LICENSE file in -# top-level folder for each specific model found within the models/ directory at -# the top-level of this source tree. diff --git a/llama_stack/models/llama/llama3_2/prompts_text.py b/llama_stack/models/llama/llama3_2/prompts_text.py index 7bc7e3219..7a1f9887c 100644 --- a/llama_stack/models/llama/llama3_2/prompts_text.py +++ b/llama_stack/models/llama/llama3_2/prompts_text.py @@ -4,12 +4,6 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the terms described in the LICENSE file in -# top-level folder for each specific model found within the models/ directory at -# the top-level of this source tree. import json import textwrap diff --git a/llama_stack/models/llama/llama3_2/prompts_vision.py b/llama_stack/models/llama/llama3_2/prompts_vision.py index b1ede418b..b0f11cab6 100644 --- a/llama_stack/models/llama/llama3_2/prompts_vision.py +++ b/llama_stack/models/llama/llama3_2/prompts_vision.py @@ -4,13 +4,6 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the terms described in the LICENSE file in -# top-level folder for each specific model found within the models/ directory at -# the top-level of this source tree. - import textwrap from pathlib import Path diff --git a/llama_stack/providers/inline/inference/meta_reference/llama4/args.py b/llama_stack/models/llama/llama4/args.py similarity index 91% rename from llama_stack/providers/inline/inference/meta_reference/llama4/args.py rename to llama_stack/models/llama/llama4/args.py index 046448ef6..6d7c1d409 100644 --- a/llama_stack/providers/inline/inference/meta_reference/llama4/args.py +++ b/llama_stack/models/llama/llama4/args.py @@ -4,13 +4,6 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the terms described in the LICENSE file in -# top-level folder for each specific model found within the models/ directory at -# the top-level of this source tree. - from enum import Enum from typing import Optional diff --git a/llama_stack/models/llama/llama4/chat_format.py b/llama_stack/models/llama/llama4/chat_format.py index c873012d6..160bb00f8 100644 --- a/llama_stack/models/llama/llama4/chat_format.py +++ b/llama_stack/models/llama/llama4/chat_format.py @@ -13,7 +13,7 @@ import torch from PIL import Image as PIL_Image # TODO: either fork these or move them to the common package -from llama_stack.models.llama.datatypes import ( +from ..datatypes import ( BuiltinTool, RawContent, RawMediaItem, @@ -24,16 +24,10 @@ from llama_stack.models.llama.datatypes import ( ToolCall, ToolPromptFormat, ) -from llama_stack.models.llama.llama3.tool_utils import ToolUtils -from llama_stack.providers.inline.inference.meta_reference.llama4.args import VisionArgs -from llama_stack.providers.inline.inference.meta_reference.llama4.datatypes import ( - LLMInput, -) -from llama_stack.providers.inline.inference.meta_reference.llama4.preprocess import ( - ResizeNormalizeImageTransform, - VariableSizeImageTransform, -) - +from ..llama3.tool_utils import ToolUtils +from .args import VisionArgs +from .datatypes import LLMInput +from .preprocess import ResizeNormalizeImageTransform, VariableSizeImageTransform from .tokenizer import Tokenizer @@ -54,7 +48,7 @@ class TransformedImage: aspect_ratio: Tuple[int, int] -def convert_rgba_to_rgb(image: PIL_Image.Image, bg: Tuple[int, int, int] = (255, 255, 255)) -> PIL_Image.Image: +def convert_image_to_rgb(image: PIL_Image.Image, bg: Tuple[int, int, int] = (255, 255, 255)) -> PIL_Image.Image: if image.mode == "RGBA": image.load() # for png.split() new_img = PIL_Image.new("RGB", image.size, bg) @@ -171,7 +165,7 @@ class ChatFormat: bytes_io = io.BytesIO(c.data) if isinstance(c.data, bytes) else c.data image = PIL_Image.open(bytes_io) - image = convert_rgba_to_rgb(image) + image = convert_image_to_rgb(image) image_tiles, ar = self.dynamic_image_transform(image, max_num_chunks=self.max_num_chunks) if image_tiles.shape[0] > 1: diff --git a/llama_stack/providers/inline/inference/meta_reference/llama4/datatypes.py b/llama_stack/models/llama/llama4/datatypes.py similarity index 85% rename from llama_stack/providers/inline/inference/meta_reference/llama4/datatypes.py rename to llama_stack/models/llama/llama4/datatypes.py index bb1c19a12..27174db63 100644 --- a/llama_stack/providers/inline/inference/meta_reference/llama4/datatypes.py +++ b/llama_stack/models/llama/llama4/datatypes.py @@ -4,13 +4,6 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the terms described in the LICENSE file in -# top-level folder for each specific model found within the models/ directory at -# the top-level of this source tree. - from dataclasses import dataclass from typing import List, Optional, Union diff --git a/llama_stack/providers/inline/inference/meta_reference/llama4/ffn.py b/llama_stack/models/llama/llama4/ffn.py similarity index 100% rename from llama_stack/providers/inline/inference/meta_reference/llama4/ffn.py rename to llama_stack/models/llama/llama4/ffn.py diff --git a/llama_stack/providers/inline/inference/meta_reference/llama4/generation.py b/llama_stack/models/llama/llama4/generation.py similarity index 72% rename from llama_stack/providers/inline/inference/meta_reference/llama4/generation.py rename to llama_stack/models/llama/llama4/generation.py index de900ce8d..7a4087c8f 100644 --- a/llama_stack/providers/inline/inference/meta_reference/llama4/generation.py +++ b/llama_stack/models/llama/llama4/generation.py @@ -10,40 +10,28 @@ import json import os import sys import time -from enum import Enum from pathlib import Path from typing import Callable, Generator, List, Optional import torch import torch.nn.functional as F from fairscale.nn.model_parallel.initialize import ( - get_model_parallel_rank, initialize_model_parallel, model_parallel_is_initialized, ) from termcolor import cprint -from llama_stack.models.llama.llama4.chat_format import ( - ChatFormat, - RawContent, - RawMessage, -) -from llama_stack.models.llama.llama4.tokenizer import Tokenizer - -from ..common import TokenResult +from ..checkpoint import maybe_reshard_state_dict +from ..datatypes import GenerationResult, QuantizationMode from .args import ModelArgs +from .chat_format import ChatFormat, RawContent, RawMessage from .datatypes import LLMInput, MaskedEmbedding, TransformerInput from .model import Transformer +from .tokenizer import Tokenizer torch.serialization.add_safe_globals([io.BytesIO, codecs.encode]) -class QuantizationMode(str, Enum): - none = "none" - fp8_mixed = "fp8_mixed" - int4_mixed = "int4_mixed" - - class Llama4: @staticmethod def build( @@ -51,7 +39,7 @@ class Llama4: max_seq_len: int, max_batch_size: int, world_size: Optional[int] = None, - quantization_mode: Optional[str] = None, + quantization_mode: Optional[QuantizationMode] = None, seed: int = 1, ): if not torch.distributed.is_initialized(): @@ -72,11 +60,9 @@ class Llama4: start_time = time.time() - checkpoints = sorted(Path(ckpt_dir).glob("*.pth")) - assert len(checkpoints) > 0, f"no checkpoint files found in {ckpt_dir}" - assert world_size == len(checkpoints), ( - f"Loading a checkpoint for MP={len(checkpoints)} but world size is {world_size}" - ) + ckpt_paths = sorted(Path(ckpt_dir).glob("*.pth")) + assert len(ckpt_paths) > 0, f"no checkpoint files found in {ckpt_dir}" + print(f"Loading a checkpoint (shards={len(ckpt_paths)}, current-mp-size={world_size})") with open(Path(ckpt_dir) / "params.json", "r") as f: params = json.loads(f.read()) @@ -93,10 +79,11 @@ class Llama4: assert model_args.vocab_size == tokenizer.n_words, f"{model_args.vocab_size=} vs. {tokenizer.n_words=} mismatch" print("Model args:\n", model_args.model_dump_json(indent=2)) - ckpt_path = checkpoints[get_model_parallel_rank()] - print(f"Loading checkpoint from {ckpt_dir}...") - with open(ckpt_path, "rb") as f: - checkpoint = torch.load(f, map_location="cpu", weights_only=True) + state_dict = maybe_reshard_state_dict( + ckpt_paths, + n_kv_heads=model_args.n_kv_heads if model_args.n_kv_heads else model_args.n_heads, + moe_num_experts=model_args.moe_args.num_experts, + ) print("Loaded checkpoint") if quantization_mode == QuantizationMode.fp8_mixed or quantization_mode == QuantizationMode.int4_mixed: from .quantization.loader import convert_to_quantized_model @@ -104,9 +91,9 @@ class Llama4: torch.set_default_tensor_type(torch.BFloat16Tensor) model = Transformer(model_args) print("Loading state dict...") - model.load_state_dict(checkpoint, strict=False) + model.load_state_dict(state_dict, strict=False) print("Done...") - model = convert_to_quantized_model(model, ckpt_dir) + model = convert_to_quantized_model(model, ckpt_dir, quantization_mode) else: if torch.cuda.is_bf16_supported(): torch.set_default_tensor_type(torch.cuda.BFloat16Tensor) @@ -115,7 +102,7 @@ class Llama4: model = Transformer(model_args) print("Loading state dict...") - model.load_state_dict(checkpoint, strict=False) + model.load_state_dict(state_dict, strict=False) print("Done...") print(f"Loaded in {time.time() - start_time:.2f} seconds") @@ -130,7 +117,7 @@ class Llama4: @torch.inference_mode() def generate( self, - llm_input: LLMInput, + llm_inputs: List[LLMInput], temperature: float = 0.6, top_p: float = 0.9, max_gen_len: Optional[int] = None, @@ -138,22 +125,20 @@ class Llama4: echo: bool = False, print_model_input: bool = False, logits_processor: Optional[Callable[[torch.Tensor, torch.Tensor], torch.Tensor]] = None, - ) -> Generator: + ) -> Generator[List[GenerationResult], None, None]: if max_gen_len is None or max_gen_len == 0 or max_gen_len >= self.model.args.max_seq_len: max_gen_len = self.model.args.max_seq_len - 1 params = self.model.args print_model_input = print_model_input or os.environ.get("LLAMA_MODELS_DEBUG", "0") == "1" - if print_model_input and get_model_parallel_rank() == 0: - tokens_to_print = list(llm_input.tokens) - cprint( - "Input to model:\n" + self.tokenizer.decode(tokens_to_print) + "\n", - "red", - ) - prompt_tokens = [llm_input.tokens] + if print_model_input: + cprint("Input to model:\n", "yellow") + for inp in llm_inputs: + cprint(self.tokenizer.decode(inp.tokens), "grey") + prompt_tokens = [inp.tokens for inp in llm_inputs] - bsz = 1 + bsz = len(llm_inputs) assert bsz <= params.max_batch_size, (bsz, params.max_batch_size) min_prompt_len = min(len(t) for t in prompt_tokens) @@ -176,24 +161,33 @@ class Llama4: input_text_mask = tokens != pad_id if echo: - for i, t in enumerate(llm_input.tokens): - yield TokenResult( - token=t, - text=self.tokenizer.decode([t]), - logprobs=(token_logprobs[0, i : i + 1].tolist() if logprobs else None), - ) + for i in range(max_prompt_len): + results = [] + for j, t in enumerate(tokens[:, i]): + results.append( + GenerationResult( + token=t.item(), + text=self.tokenizer.decode([t.item()]), + source="input", + logprobs=(token_logprobs[j, i : i + 1].tolist() if logprobs else None), + batch_idx=j, + finished=False, + ignore_token=t.item() == pad_id, + ) + ) + yield results stop_tokens = torch.tensor(self.tokenizer.stop_tokens, device="cuda") prev_pos = 0 for cur_pos in range(min_prompt_len, total_len): image_embedding = None - if prev_pos == 0 and llm_input.images is not None and len(llm_input.images) > 0: + if prev_pos == 0 and any(inp.images is not None and len(inp.images) > 0 for inp in llm_inputs): image_mask = tokens[:, prev_pos:cur_pos] == self.tokenizer.special_tokens["<|patch|>"] image_mask = image_mask.unsqueeze(-1) h = self.model.tok_embeddings(tokens[:, prev_pos:cur_pos]) - image_batch = [llm_input.images] + image_batch = [inp.images if inp.images is not None else [] for inp in llm_inputs] image_embedding = MaskedEmbedding( embedding=self.model.vision_embeddings(image_batch, image_mask, h), mask=image_mask, @@ -229,11 +223,21 @@ class Llama4: ignore_index=pad_id, ) eos_reached |= (~input_text_mask[:, cur_pos]) & (torch.isin(next_token, stop_tokens)) - yield TokenResult( - token=next_token[0].item(), - text=self.tokenizer.decode(next_token.tolist()), - logprobs=(token_logprobs[:, cur_pos : cur_pos + 1][0].tolist() if logprobs else None), - ) + + results = [] + for idx, t in enumerate(next_token): + results.append( + GenerationResult( + token=t.item(), + text=self.tokenizer.decode([t.item()]), + source="output", + logprobs=(token_logprobs[idx, cur_pos : cur_pos + 1].tolist() if logprobs else None), + batch_idx=idx, + finished=eos_reached[idx], + ignore_token=cur_pos < len(prompt_tokens[idx]), + ) + ) + yield results prev_pos = cur_pos if all(eos_reached): @@ -241,68 +245,47 @@ class Llama4: def completion( self, - content: RawContent, + contents: List[RawContent], temperature: float = 0.6, top_p: float = 0.9, max_gen_len: Optional[int] = None, logprobs: bool = False, echo: bool = False, - ) -> Generator: - llm_input = self.formatter.encode_content(content) + ) -> Generator[List[GenerationResult], None, None]: + llm_inputs = [self.formatter.encode_content(c) for c in contents] for result in self.generate( - llm_input=llm_input, + llm_inputs=llm_inputs, temperature=temperature, top_p=top_p, max_gen_len=max_gen_len, logprobs=logprobs, echo=echo, ): - if result.token in self.tokenizer.stop_tokens: - break yield result + if all(r.finished for r in result): + break def chat_completion( self, - messages: List[RawMessage], + messages_batch: List[List[RawMessage]], temperature: float = 0.6, top_p: float = 0.9, max_gen_len: Optional[int] = None, logprobs: bool = False, echo: bool = False, - ) -> Generator: - llm_input = self.formatter.encode_dialog_prompt(messages) + ) -> Generator[List[GenerationResult], None, None]: + llm_inputs = [self.formatter.encode_dialog_prompt(messages) for messages in messages_batch] for result in self.generate( - llm_input=llm_input, + llm_inputs=llm_inputs, temperature=temperature, top_p=top_p, max_gen_len=max_gen_len, logprobs=logprobs, echo=echo, ): - if result.token in self.tokenizer.stop_tokens: - break yield result - - def chat_completion_raw( - self, - messages: List[RawMessage], - temperature: float = 0.6, - top_p: float = 0.9, - max_gen_len: Optional[int] = None, - logprobs: bool = False, - ): - llm_input = self.formatter.encode_dialog_prompt(messages) - output_tokens = [] - for result in self.generate( - llm_input=llm_input, - temperature=temperature, - top_p=top_p, - max_gen_len=max_gen_len, - logprobs=logprobs, - ): - output_tokens.append(result.token) - - return llm_input.tokens, output_tokens + if all(r.finished for r in result): + break def sample_top_p(probs, p): diff --git a/llama_stack/providers/inline/inference/meta_reference/llama4/model.py b/llama_stack/models/llama/llama4/model.py similarity index 97% rename from llama_stack/providers/inline/inference/meta_reference/llama4/model.py rename to llama_stack/models/llama/llama4/model.py index a35d6857f..08fac7714 100644 --- a/llama_stack/providers/inline/inference/meta_reference/llama4/model.py +++ b/llama_stack/models/llama/llama4/model.py @@ -4,16 +4,6 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the terms described in the LICENSE file in -# top-level folder for each specific model found within the models/ directory at -# the top-level of this source tree. - -# Copyright (c) Meta Platforms, Inc. and affiliates. -# This software may be used and distributed in accordance with the terms of the Llama 3 Community License Agreement. - import math from typing import Any, Dict, List, Optional, Tuple @@ -184,7 +174,6 @@ class Attention(nn.Module): self.head_dim, ) ).cuda() - self.qk_norm = None if self.use_qk_norm: self.qk_norm = L2Norm(args.norm_eps) diff --git a/llama_stack/providers/inline/inference/meta_reference/llama4/moe.py b/llama_stack/models/llama/llama4/moe.py similarity index 87% rename from llama_stack/providers/inline/inference/meta_reference/llama4/moe.py rename to llama_stack/models/llama/llama4/moe.py index 8cecab7dd..2ce49e915 100644 --- a/llama_stack/providers/inline/inference/meta_reference/llama4/moe.py +++ b/llama_stack/models/llama/llama4/moe.py @@ -100,31 +100,21 @@ class Experts(nn.Module): class MoE(torch.nn.Module): """ - This EC implementation is modified from the original EC module. - We refactored the token permutation and unpermutation logic and added support to tp and dp2ep sharding. - This module supports 3 sharding methods of the experts: - - tp: each TP rank has n_experts experts. Experts are sharded following the conventional row/column-parallel TP sharding. - - tp2ep: each TP rank has n_experts/tp experts. Experts are not sharded. - - dp2ep: each EP rank has n_experts/ep experts. Experts are sharded following the row/column-parallel TP sharding. Tensors used in this module are annotated with the suffixes that indicate the shape of the tensor. Several commonly used annotations include: - a: bsz*slen - E: number of experts - e: number of local experts per ep (n_experts/ep) - - et: number of local experts per tp (n_experts/tp) - D: hidden dimension - d: D/tp - F: model dimension - - f: F/tp (used in column/row-parallel linear) - G: number of tokens per expert (a * capacity_factor / E) - g: number of tokens per expert per TP rank (i.e., G/TP) - - GG: G*EP (number of tokens per expert received via inter-EP a2a when ag_along_first_dim=False) - - gg: g*EP (number of tokens per expert received via inter-EP a2a when ag_along_first_dim=True) Examples: x_aD [a, D] routed_in_etG_D [et*G, D] - x_eGGD: [e, GG, D] + x_eGD: [e, G, D] """ def __init__( @@ -207,13 +197,13 @@ class MoE(torch.nn.Module): routed_in_EG_D = routed_in_EG_D * router_scores.reshape(-1, 1) out_aD = self.shared_expert(x_aD) - routed_out_egg_D = self.experts(routed_in_EG_D.detach()) + routed_out_eg_D = self.experts(routed_in_EG_D.detach()) router_indices_EG_D = router_indices.reshape(-1, 1).expand(-1, D) out_aD.scatter_add_( dim=0, index=router_indices_EG_D, - src=routed_out_egg_D.view(-1, D), + src=routed_out_eg_D.view(-1, D), ) out_aD = reduce_from_model_parallel_region(out_aD) return out_aD.view(-1, slen, D) diff --git a/llama_stack/providers/inline/inference/meta_reference/llama4/preprocess.py b/llama_stack/models/llama/llama4/preprocess.py similarity index 100% rename from llama_stack/providers/inline/inference/meta_reference/llama4/preprocess.py rename to llama_stack/models/llama/llama4/preprocess.py diff --git a/llama_stack/models/llama/llama4/prompts.py b/llama_stack/models/llama/llama4/prompts.py index 97f573ef8..13b96359a 100644 --- a/llama_stack/models/llama/llama4/prompts.py +++ b/llama_stack/models/llama/llama4/prompts.py @@ -4,20 +4,13 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the terms described in the LICENSE file in -# top-level folder for each specific model found within the models/ directory at -# the top-level of this source tree. - import textwrap from io import BytesIO from pathlib import Path from typing import List -from llama_stack.models.llama.datatypes import RawMediaItem, RawMessage, RawTextItem -from llama_stack.models.llama.prompt_format import ( +from ..datatypes import RawMediaItem, RawMessage, RawTextItem +from ..prompt_format import ( Llama4UseCase, TextCompletionContent, UseCase, diff --git a/llama_stack/models/llama/llama4/quantization/__init__.py b/llama_stack/models/llama/llama4/quantization/__init__.py new file mode 100644 index 000000000..756f351d8 --- /dev/null +++ b/llama_stack/models/llama/llama4/quantization/__init__.py @@ -0,0 +1,5 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. diff --git a/llama_stack/providers/inline/inference/meta_reference/llama4/quantization/loader.py b/llama_stack/models/llama/llama4/quantization/loader.py similarity index 70% rename from llama_stack/providers/inline/inference/meta_reference/llama4/quantization/loader.py rename to llama_stack/models/llama/llama4/quantization/loader.py index 69aa309fa..b50432896 100644 --- a/llama_stack/providers/inline/inference/meta_reference/llama4/quantization/loader.py +++ b/llama_stack/models/llama/llama4/quantization/loader.py @@ -6,20 +6,29 @@ import logging import os -from typing import Optional +from typing import Callable, Optional import torch from fairscale.nn.model_parallel.initialize import get_model_parallel_rank -from torch import Tensor +from torch import Tensor, nn from torch.nn import functional as F -from ..generation import QuantizationMode +from ...datatypes import QuantizationMode from ..model import Transformer, TransformerBlock from ..moe import MoE log = logging.getLogger(__name__) +def swiglu_wrapper_no_reduce( + self, + x: Tensor, +): + from ...quantize_impls import ffn_swiglu + + return ffn_swiglu(x, self.w1.weight, self.w3.weight, self.w2.weight) + + def experts_batched_swiglu_wrapper( self, x: Tensor, # (e, g, D) @@ -51,24 +60,30 @@ def convert_to_quantized_model( rank = get_model_parallel_rank() + def should_quantize_block(block: nn.Module) -> bool: + if not isinstance(block, TransformerBlock): + return False + + is_moe = isinstance(block.feed_forward, MoE) + if quantization_mode == QuantizationMode.fp8_mixed: + # skip quantization on first and last layers + return is_moe and not (block.layer_id == 0 or block.layer_id == (model.n_layers - 1)) + + return is_moe + use_rich_progress = use_rich_progress and rank == 0 - progress, log_status, update_status = logging_callbacks(use_rich_progress, rank, model) + progress, log_status, update_status = logging_callbacks(use_rich_progress, rank, model, should_quantize_block) if quantization_mode == QuantizationMode.int4_mixed: int4_scales_path = os.path.join(checkpoint_dir, f"int4_scales_{rank}.pt") - int4_zero_points_path = os.path.join(checkpoint_dir, f"int4_zero_points_{rank}.pt") if os.path.isfile(int4_scales_path): log_status(f"Rank {rank}: Loading int4 scales") int4_scales = torch.load(int4_scales_path, weights_only=True) - int4_zero_points = torch.load(int4_zero_points_path, weights_only=True) def apply_quantization(key, weight): scale = int4_scales[key] - zero_point = int4_zero_points[key] return load_int4( weight, scale, - zero_point, - fp8_activation_scale_ub, output_device=torch.device("cuda"), ) @@ -77,6 +92,7 @@ def convert_to_quantized_model( def apply_quantization(_, weight): return quantize_int4(weight, fp8_activation_scale_ub, output_device=torch.device("cuda")) + else: fp8_scales_path = os.path.join(checkpoint_dir, f"fp8_scales_{rank}.pt") if os.path.isfile(fp8_scales_path): @@ -104,33 +120,38 @@ def convert_to_quantized_model( progress.start() for _, block in model.named_modules(): - if isinstance(block, TransformerBlock): - # Skip quantization on first and last layers - if block.layer_id == 0 or block.layer_id == (model.n_layers - 1): - continue + if not should_quantize_block(block): + continue - # Skip quantization on dense layers - if not isinstance(block.feed_forward, MoE): - continue + update_status(f"Rank {rank} - Layer {block.layer_id}") - update_status(f"Rank {rank} - Layer {block.layer_id}") + # Quantize only routed experts, not shared + prefix = f"layers.{block.layer_id}.feed_forward" + moe = block.feed_forward + moe.experts.batched_swiglu = experts_batched_swiglu_wrapper.__get__(moe.experts) - # Quantize only routed experts, not shared - prefix = f"layers.{block.layer_id}.feed_forward" - moe = block.feed_forward - moe.experts.batched_swiglu = experts_batched_swiglu_wrapper.__get__(moe.experts) + for key in ("w1", "w3", "w2"): + param = getattr(moe.experts, key) + update_status(f"Rank {rank} - Layer {block.layer_id} - MoE {key}") + setattr( + moe.experts, + key, + apply_quantization( + f"{prefix}.experts.{key}", + param.transpose(1, 2).contiguous(), + ), + ) + if quantization_mode == QuantizationMode.int4_mixed: + # Quantize shared experts + moe.shared_expert.forward = swiglu_wrapper_no_reduce.__get__(moe.shared_expert) for key in ("w1", "w3", "w2"): - param = getattr(moe.experts, key) - update_status(f"Rank {rank} - Layer {block.layer_id} - MoE {key}") - setattr( - moe.experts, - key, - apply_quantization(f"{prefix}.experts.{key}", param.transpose(1, 2).contiguous()), - ) + param = getattr(moe.shared_expert, key) + update_status(f"Rank {rank} - Layer {block.layer_id} - MoE shared expert {key}") + param.weight = apply_quantization(f"{prefix}.shared_expert.{key}", param.weight) - processed_blocks += 1 - update_status(message=None, completed=processed_blocks) + processed_blocks += 1 + update_status(message=None, completed=processed_blocks) update_status(f"Rank {rank} - Moving parameters to CUDA") @@ -149,7 +170,12 @@ def convert_to_quantized_model( # fp8/int4 loading can be very slow so we add progress bars to make life slightly better -def logging_callbacks(use_rich_progress: bool, rank: int, model: Transformer): +def logging_callbacks( + use_rich_progress: bool, + rank: int, + model: Transformer, + should_quantize_block: Callable[[nn.Module], bool], +): console = None if use_rich_progress: from rich.console import Console @@ -162,15 +188,7 @@ def logging_callbacks(use_rich_progress: bool, rank: int, model: Transformer): elif rank == 0: # Only log from rank 0 for non-rich logging log.info(message) - total_blocks = sum( - 1 - for _, block in model.named_modules() - if ( - isinstance(block, TransformerBlock) - and not (block.layer_id == 0 or block.layer_id == (model.n_layers - 1)) - and isinstance(block.feed_forward, MoE) - ) - ) + total_blocks = sum(1 for _, block in model.named_modules() if should_quantize_block(block)) progress = None if use_rich_progress: from rich.progress import ( diff --git a/llama_stack/models/llama/llama4/tokenizer.py b/llama_stack/models/llama/llama4/tokenizer.py index c1347daca..4d271e5fd 100644 --- a/llama_stack/models/llama/llama4/tokenizer.py +++ b/llama_stack/models/llama/llama4/tokenizer.py @@ -4,9 +4,6 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -# Copyright (c) Meta Platforms, Inc. and affiliates. -# This software may be used and distributed in accordance with the terms of the Llama 3 Community License Agreement. - import os from logging import getLogger from pathlib import Path @@ -59,8 +56,6 @@ LLAMA4_TEXT_POST_TRAIN_SPECIAL_TOKENS = [ "<|text_post_train_reserved_special_token_3|>", "<|text_post_train_reserved_special_token_4|>", "<|text_post_train_reserved_special_token_5|>", - "<|python_start|>", - "<|python_end|>", "<|finetune_right_pad|>", ] + get_reserved_special_tokens( "text_post_train", 61, 6 @@ -85,8 +80,23 @@ LLAMA4_VISION_SPECIAL_TOKENS = [ "vision", 1041, 7 ) # <|vision_reserved_special_token_7|>, ..., <|vision_reserved_special_token_1047|> +# 201134, ..., 201143 +LLAMA4_REASONING_SPECIAL_TOKENS = [ + "<|reasoning_reserved_special_token_0|>", + "<|reasoning_reserved_special_token_1|>", + "<|reasoning_reserved_special_token_2|>", + "<|reasoning_reserved_special_token_3|>", + "<|reasoning_reserved_special_token_4|>", + "<|reasoning_reserved_special_token_5|>", + "<|reasoning_reserved_special_token_6|>", + "<|reasoning_reserved_special_token_7|>", + "<|reasoning_thinking_start|>", + "<|reasoning_thinking_end|>", +] -LLAMA4_SPECIAL_TOKENS = LLAMA4_TEXT_POST_TRAIN_SPECIAL_TOKENS + LLAMA4_VISION_SPECIAL_TOKENS +LLAMA4_SPECIAL_TOKENS = ( + LLAMA4_TEXT_POST_TRAIN_SPECIAL_TOKENS + LLAMA4_VISION_SPECIAL_TOKENS + LLAMA4_REASONING_SPECIAL_TOKENS +) BASIC_SPECIAL_TOKENS = [ "<|begin_of_text|>", @@ -155,6 +165,9 @@ class Tokenizer: self.eot_id: int = self.special_tokens["<|eot|>"] self.eom_id: int = self.special_tokens["<|eom|>"] + self.thinking_start_id: int = self.special_tokens["<|reasoning_thinking_start|>"] + self.thinking_end_id: int = self.special_tokens["<|reasoning_thinking_end|>"] + self.stop_tokens = [ self.eos_id, self.special_tokens["<|eom|>"], diff --git a/llama_stack/providers/inline/inference/meta_reference/llama4/vision/embedding.py b/llama_stack/models/llama/llama4/vision/embedding.py similarity index 96% rename from llama_stack/providers/inline/inference/meta_reference/llama4/vision/embedding.py rename to llama_stack/models/llama/llama4/vision/embedding.py index 73b29cbef..ed7659a73 100644 --- a/llama_stack/providers/inline/inference/meta_reference/llama4/vision/embedding.py +++ b/llama_stack/models/llama/llama4/vision/embedding.py @@ -4,13 +4,6 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the terms described in the LICENSE file in -# top-level folder for each specific model found within the models/ directory at -# the top-level of this source tree. - import math from typing import Any, Callable, Dict, List diff --git a/llama_stack/providers/inline/inference/meta_reference/llama4/vision/encoder.py b/llama_stack/models/llama/llama4/vision/encoder.py similarity index 100% rename from llama_stack/providers/inline/inference/meta_reference/llama4/vision/encoder.py rename to llama_stack/models/llama/llama4/vision/encoder.py diff --git a/llama_stack/models/llama/prompt_format.py b/llama_stack/models/llama/prompt_format.py index 695c0bf74..edb34620c 100644 --- a/llama_stack/models/llama/prompt_format.py +++ b/llama_stack/models/llama/prompt_format.py @@ -28,9 +28,6 @@ from llama_stack.models.llama.datatypes import ( ToolPromptFormat, ) from llama_stack.models.llama.llama4.tokenizer import Tokenizer -from llama_stack.providers.inline.inference.meta_reference.llama4.datatypes import ( - LLMInput, -) from .llama3.interface import LLama31Interface from .llama3.template_data import ( @@ -76,21 +73,22 @@ class UseCase(BaseModel): text += dialog text += "\n\n" continue - - elif isinstance(dialog, TextCompletionContent): - input_tokens, output_tokens = generator.text_completion_raw( - dialog.content, - temperature=0.1, - top_p=0.95, - max_gen_len=64, - ) else: - input_tokens, output_tokens = generator.chat_completion_raw( - dialog, - temperature=0.0, - top_p=0.95, - max_gen_len=self.max_gen_len, + batch = [dialog] + method = ( + generator.completion if isinstance(dialog, TextCompletionContent) else generator.chat_completion ) + input_tokens = [] + output_tokens = [] + for token_results in method(batch, echo=True, temperature=0.1, top_p=0.95): + result = token_results[0] + if result.source == "input": + input_tokens.append(result.token) + else: + output_tokens.append(result.token) + + if result.finished: + break text += "##### Input Prompt Format\n" # FIXME: This is added to undo the hack in chat_formatter where @@ -126,27 +124,27 @@ class Llama4UseCase(UseCase): text = "" tokenizer = Tokenizer.get_instance() - temperature = 0.0 for dialog in self.dialogs: if isinstance(dialog, str): text += dialog text += "\n\n" continue - - elif isinstance(dialog, TextCompletionContent): - # TODO pass the raw input and do the encoding in the text completion function - input_tokens = tokenizer.encode(dialog.content, bos=True, eos=False) - llm_input = LLMInput(tokens=input_tokens) - output_tokens, decoded_tokens, token_logprobs = generator.text_completion_raw( - llm_input, temperature=temperature, max_gen_len=self.max_gen_len - ) - else: - input_tokens, output_tokens = generator.chat_completion_raw( - dialog, - temperature=temperature, - max_gen_len=self.max_gen_len, + batch = [dialog] + method = ( + generator.completion if isinstance(dialog, TextCompletionContent) else generator.chat_completion ) + input_tokens = [] + output_tokens = [] + for token_results in method(batch, echo=True, temperature=0.0): + result = token_results[0] + if result.source == "input": + input_tokens.append(result.token) + else: + output_tokens.append(result.token) + + if result.finished: + break text += "##### Input Prompt Format\n" text += _code_block(tokenizer.decode(input_tokens)) diff --git a/llama_stack/providers/inline/inference/meta_reference/quantize_impls.py b/llama_stack/models/llama/quantize_impls.py similarity index 100% rename from llama_stack/providers/inline/inference/meta_reference/quantize_impls.py rename to llama_stack/models/llama/quantize_impls.py diff --git a/llama_stack/models/llama/sku_list.py b/llama_stack/models/llama/sku_list.py index dd3144bb0..513481831 100644 --- a/llama_stack/models/llama/sku_list.py +++ b/llama_stack/models/llama/sku_list.py @@ -4,24 +4,15 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the terms described in the LICENSE file in -# top-level folder for each specific model found within the models/ directory at -# the top-level of this source tree. - from dataclasses import dataclass from functools import lru_cache from typing import List, Optional -from .datatypes import ( +from .sku_types import ( CheckpointQuantizationFormat, CoreModelId, Model, ModelFamily, - SamplingParams, - TopPSamplingStrategy, ) LLAMA2_VOCAB_SIZE = 32000 @@ -47,15 +38,6 @@ def all_registered_models() -> List[Model]: ) -def recommended_sampling_params() -> SamplingParams: - return SamplingParams( - strategy=TopPSamplingStrategy( - temperature=1.0, - top_p=0.9, - ) - ) - - def llama2_family() -> List[Model]: return [ *llama2_base_models(), @@ -150,7 +132,6 @@ def llama2_base_models() -> List[Model]: core_model_id=CoreModelId.llama2_7b, description="Llama 2 7b model", huggingface_repo="meta-llama/Llama-2-7b", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 4096, "n_layers": 32, @@ -169,7 +150,6 @@ def llama2_base_models() -> List[Model]: core_model_id=CoreModelId.llama2_13b, description="Llama 2 13b model", huggingface_repo="meta-llama/Llama-2-13b", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 5120, "n_layers": 40, @@ -188,7 +168,6 @@ def llama2_base_models() -> List[Model]: core_model_id=CoreModelId.llama2_70b, description="Llama 2 70b model", huggingface_repo="meta-llama/Llama-2-70b", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 8192, "n_layers": 80, @@ -230,7 +209,6 @@ def llama3_base_models() -> List[Model]: core_model_id=CoreModelId.llama3_70b, description="Llama 3 70b model", huggingface_repo="meta-llama/Llama-3-70B", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 8192, "n_layers": 80, @@ -254,7 +232,6 @@ def llama3_1_base_models() -> List[Model]: core_model_id=CoreModelId.llama3_1_8b, description="Llama 3.1 8b model", huggingface_repo="meta-llama/Llama-3.1-8B", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 4096, "n_layers": 32, @@ -273,7 +250,6 @@ def llama3_1_base_models() -> List[Model]: core_model_id=CoreModelId.llama3_1_70b, description="Llama 3.1 70b model", huggingface_repo="meta-llama/Llama-3.1-70B", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 8192, "n_layers": 80, @@ -293,7 +269,6 @@ def llama3_1_base_models() -> List[Model]: variant="bf16-mp8", description="Llama 3.1 405b model (BF16 weights)", huggingface_repo="meta-llama/Llama-3.1-405B", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 16384, "n_layers": 126, @@ -313,7 +288,6 @@ def llama3_1_base_models() -> List[Model]: description="Llama 3.1 405b model (FP8 quantized)", huggingface_repo="meta-llama/Llama-3.1-405B-FP8", quantization_format=CheckpointQuantizationFormat.fp8_mixed, - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 16384, "n_layers": 126, @@ -333,7 +307,6 @@ def llama3_1_base_models() -> List[Model]: variant="bf16-mp16", description="Llama 3.1 405b model (BF16 weights for mp16)", huggingface_repo="meta-llama/Llama-3.1-405B", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 16384, "n_layers": 126, @@ -357,7 +330,6 @@ def llama3_2_base_models() -> List[Model]: core_model_id=CoreModelId.llama3_2_1b, description="Llama 3.2 1b model", huggingface_repo="meta-llama/Llama-3.2-1B", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 2048, "n_layers": 16, @@ -376,7 +348,6 @@ def llama3_2_base_models() -> List[Model]: core_model_id=CoreModelId.llama3_2_3b, description="Llama 3.2 3b model", huggingface_repo="meta-llama/Llama-3.2-3B", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 3072, "n_layers": 28, @@ -395,7 +366,6 @@ def llama3_2_base_models() -> List[Model]: core_model_id=CoreModelId.llama3_2_11b_vision, description="Llama 3.2 11b vision model", huggingface_repo="meta-llama/Llama-3.2-11B-Vision", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 4096, "n_layers": 32, @@ -417,7 +387,6 @@ def llama3_2_base_models() -> List[Model]: core_model_id=CoreModelId.llama3_2_90b_vision, description="Llama 3.2 90b vision model", huggingface_repo="meta-llama/Llama-3.2-90B-Vision", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 8192, "n_layers": 80, @@ -444,7 +413,6 @@ def llama2_instruct_models() -> List[Model]: core_model_id=CoreModelId.llama2_7b_chat, description="Llama 2 7b chat model", huggingface_repo="meta-llama/Llama-2-7b-chat", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 4096, "n_layers": 32, @@ -463,7 +431,6 @@ def llama2_instruct_models() -> List[Model]: core_model_id=CoreModelId.llama2_13b_chat, description="Llama 2 13b chat model", huggingface_repo="meta-llama/Llama-2-13b-chat", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 5120, "n_layers": 40, @@ -482,7 +449,6 @@ def llama2_instruct_models() -> List[Model]: core_model_id=CoreModelId.llama2_70b_chat, description="Llama 2 70b chat model", huggingface_repo="meta-llama/Llama-2-70b-chat", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 8192, "n_layers": 80, @@ -506,7 +472,6 @@ def llama3_instruct_models() -> List[Model]: core_model_id=CoreModelId.llama3_8b_instruct, description="Llama 3 8b instruct model", huggingface_repo="meta-llama/Llama-3-8B-Instruct", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 4096, "n_layers": 32, @@ -525,7 +490,6 @@ def llama3_instruct_models() -> List[Model]: core_model_id=CoreModelId.llama3_70b_instruct, description="Llama 3 70b instruct model", huggingface_repo="meta-llama/Llama-3-70B-Instruct", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 8192, "n_layers": 80, @@ -549,7 +513,6 @@ def llama3_1_instruct_models() -> List[Model]: core_model_id=CoreModelId.llama3_1_8b_instruct, description="Llama 3.1 8b instruct model", huggingface_repo="meta-llama/Llama-3.1-8B-Instruct", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 4096, "n_layers": 32, @@ -568,7 +531,6 @@ def llama3_1_instruct_models() -> List[Model]: core_model_id=CoreModelId.llama3_1_70b_instruct, description="Llama 3.1 70b instruct model", huggingface_repo="meta-llama/Llama-3.1-70B-Instruct", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 8192, "n_layers": 80, @@ -588,7 +550,6 @@ def llama3_1_instruct_models() -> List[Model]: variant="bf16-mp8", description="Llama 3.1 405b instruct model (BF16 weights)", huggingface_repo="meta-llama/Llama-3.1-405B-Instruct", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 16384, "n_layers": 126, @@ -608,7 +569,6 @@ def llama3_1_instruct_models() -> List[Model]: description="Llama 3.1 405b instruct model (FP8 quantized)", huggingface_repo="meta-llama/Llama-3.1-405B-Instruct-FP8", quantization_format=CheckpointQuantizationFormat.fp8_mixed, - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 16384, "n_layers": 126, @@ -628,7 +588,6 @@ def llama3_1_instruct_models() -> List[Model]: variant="bf16-mp16", description="Llama 3.1 405b instruct model (BF16 weights for mp16)", huggingface_repo="meta-llama/Llama-3.1-405B-Instruct", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 16384, "n_layers": 126, @@ -684,7 +643,6 @@ def llama3_2_quantized_models() -> List[Model]: quantization_format=CheckpointQuantizationFormat.int4, description="Llama 3.2 1b INT4 quantized LoRA", huggingface_repo="meta-llama/Llama-3.2-1B-Instruct-QLORA_INT4_EO8", - recommended_sampling_params=recommended_sampling_params(), arch_args={ **arch_args_1b(), "quantization_args": { @@ -703,7 +661,6 @@ def llama3_2_quantized_models() -> List[Model]: quantization_format=CheckpointQuantizationFormat.int4, description="Llama 3.2 1b INT4 quantized SpinQuant", huggingface_repo="meta-llama/Llama-3.2-1B-Instruct-SpinQuant_INT4_EO8", - recommended_sampling_params=recommended_sampling_params(), arch_args={ **arch_args_1b(), "quantization_args": { @@ -718,7 +675,6 @@ def llama3_2_quantized_models() -> List[Model]: quantization_format=CheckpointQuantizationFormat.int4, description="Llama 3.2 3b INT4 quantized LoRA", huggingface_repo="meta-llama/Llama-3.2-3B-Instruct-QLORA_INT4_EO8", - recommended_sampling_params=recommended_sampling_params(), arch_args={ **arch_args_3b(), "quantization_args": { @@ -737,7 +693,6 @@ def llama3_2_quantized_models() -> List[Model]: quantization_format=CheckpointQuantizationFormat.int4, description="Llama 3.2 3b INT4 quantized SpinQuant", huggingface_repo="meta-llama/Llama-3.2-3B-Instruct-SpinQuant_INT4_EO8", - recommended_sampling_params=recommended_sampling_params(), arch_args={ **arch_args_3b(), "quantization_args": { @@ -755,7 +710,6 @@ def llama3_2_instruct_models() -> List[Model]: core_model_id=CoreModelId.llama3_2_1b_instruct, description="Llama 3.2 1b instruct model", huggingface_repo="meta-llama/Llama-3.2-1B-Instruct", - recommended_sampling_params=recommended_sampling_params(), arch_args=arch_args_1b(), pth_file_count=1, ), @@ -763,7 +717,6 @@ def llama3_2_instruct_models() -> List[Model]: core_model_id=CoreModelId.llama3_2_3b_instruct, description="Llama 3.2 3b instruct model", huggingface_repo="meta-llama/Llama-3.2-3B-Instruct", - recommended_sampling_params=recommended_sampling_params(), arch_args=arch_args_3b(), pth_file_count=1, ), @@ -772,7 +725,6 @@ def llama3_2_instruct_models() -> List[Model]: core_model_id=CoreModelId.llama3_2_11b_vision_instruct, description="Llama 3.2 11b vision instruct model", huggingface_repo="meta-llama/Llama-3.2-11B-Vision-Instruct", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 4096, "n_layers": 32, @@ -794,7 +746,6 @@ def llama3_2_instruct_models() -> List[Model]: core_model_id=CoreModelId.llama3_2_90b_vision_instruct, description="Llama 3.2 90b vision instruct model", huggingface_repo="meta-llama/Llama-3.2-90B-Vision-Instruct", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 8192, "n_layers": 80, @@ -821,7 +772,6 @@ def llama3_3_instruct_models() -> List[Model]: core_model_id=CoreModelId.llama3_3_70b_instruct, description="Llama 3.3 70b instruct", huggingface_repo="meta-llama/Llama-3.3-70B-Instruct", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 8192, "n_layers": 80, @@ -846,7 +796,6 @@ def safety_models() -> List[Model]: core_model_id=CoreModelId.llama_guard_3_11b_vision, description="Llama Guard v3 11b vision system safety model", huggingface_repo="meta-llama/Llama-Guard-3-11B-Vision", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 4096, "n_layers": 32, @@ -870,7 +819,6 @@ def safety_models() -> List[Model]: description="Llama Guard v3 1b 'int4' quantized system safety model", huggingface_repo="meta-llama/Llama-Guard-3-1B-INT4", quantization_format=CheckpointQuantizationFormat.int4, - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 2048, "n_layers": 12, @@ -888,7 +836,6 @@ def safety_models() -> List[Model]: core_model_id=CoreModelId.llama_guard_3_1b, description="Llama Guard v3 1b system safety model", huggingface_repo="meta-llama/Llama-Guard-3-1B", - recommended_sampling_params=recommended_sampling_params(), arch_args={ "dim": 2048, "n_layers": 16, diff --git a/llama_stack/models/llama/sku_types.py b/llama_stack/models/llama/sku_types.py new file mode 100644 index 000000000..88799b66d --- /dev/null +++ b/llama_stack/models/llama/sku_types.py @@ -0,0 +1,229 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from enum import Enum +from typing import Any, Dict, Optional + +from pydantic import BaseModel, ConfigDict, Field + + +class CheckpointQuantizationFormat(Enum): + # default format + bf16 = "bf16" + + # used for enabling fp8_rowwise inference, some weights are bf16 + fp8_mixed = "fp8-mixed" + + int8 = "int8" + + int4 = "int4" + + +class ModelFamily(Enum): + llama2 = "llama2" + llama3 = "llama3" + llama3_1 = "llama3_1" + llama3_2 = "llama3_2" + llama3_3 = "llama3_3" + llama4 = "llama4" + safety = "safety" + + +class CoreModelId(Enum): + """Each of these models is a unique "SKU". These root models can be served in various garbs (especially by quantizing them)""" + + # Llama 2 family + llama2_7b = "Llama-2-7b" + llama2_13b = "Llama-2-13b" + llama2_70b = "Llama-2-70b" + llama2_7b_chat = "Llama-2-7b-chat" + llama2_13b_chat = "Llama-2-13b-chat" + llama2_70b_chat = "Llama-2-70b-chat" + + # Llama 3 family + llama3_8b = "Llama-3-8B" + llama3_70b = "Llama-3-70B" + llama3_8b_instruct = "Llama-3-8B-Instruct" + llama3_70b_instruct = "Llama-3-70B-Instruct" + + # Llama 3.1 family + llama3_1_8b = "Llama3.1-8B" + llama3_1_70b = "Llama3.1-70B" + llama3_1_405b = "Llama3.1-405B" + llama3_1_8b_instruct = "Llama3.1-8B-Instruct" + llama3_1_70b_instruct = "Llama3.1-70B-Instruct" + llama3_1_405b_instruct = "Llama3.1-405B-Instruct" + + # Llama 3.2 family + llama3_2_1b = "Llama3.2-1B" + llama3_2_3b = "Llama3.2-3B" + llama3_2_1b_instruct = "Llama3.2-1B-Instruct" + llama3_2_3b_instruct = "Llama3.2-3B-Instruct" + llama3_2_11b_vision = "Llama3.2-11B-Vision" + llama3_2_90b_vision = "Llama3.2-90B-Vision" + llama3_2_11b_vision_instruct = "Llama3.2-11B-Vision-Instruct" + llama3_2_90b_vision_instruct = "Llama3.2-90B-Vision-Instruct" + + # Llama 3.3 family + llama3_3_70b_instruct = "Llama3.3-70B-Instruct" + + # Llama 4 family + llama4_scout_17b_16e = "Llama-4-Scout-17B-16E" + llama4_scout_17b_16e_instruct = "Llama-4-Scout-17B-16E-Instruct" + llama4_maverick_17b_128e = "Llama-4-Maverick-17B-128E" + llama4_maverick_17b_128e_instruct = "Llama-4-Maverick-17B-128E-Instruct" + + # Safety models + llama_guard_3_8b = "Llama-Guard-3-8B" + llama_guard_2_8b = "Llama-Guard-2-8B" + llama_guard_3_11b_vision = "Llama-Guard-3-11B-Vision" + llama_guard_3_1b = "Llama-Guard-3-1B" + + +def is_multimodal(model_id) -> bool: + if model_id in [ + CoreModelId.llama3_2_11b_vision, + CoreModelId.llama3_2_90b_vision, + CoreModelId.llama3_2_11b_vision_instruct, + CoreModelId.llama3_2_90b_vision_instruct, + ]: + return True + else: + return False + + +def model_family(model_id) -> ModelFamily: + if model_id in [ + CoreModelId.llama2_7b, + CoreModelId.llama2_13b, + CoreModelId.llama2_70b, + CoreModelId.llama2_7b_chat, + CoreModelId.llama2_13b_chat, + CoreModelId.llama2_70b_chat, + ]: + return ModelFamily.llama2 + elif model_id in [ + CoreModelId.llama3_8b, + CoreModelId.llama3_70b, + CoreModelId.llama3_8b_instruct, + CoreModelId.llama3_70b_instruct, + ]: + return ModelFamily.llama3 + elif model_id in [ + CoreModelId.llama3_1_8b, + CoreModelId.llama3_1_70b, + CoreModelId.llama3_1_405b, + CoreModelId.llama3_1_8b_instruct, + CoreModelId.llama3_1_70b_instruct, + CoreModelId.llama3_1_405b_instruct, + ]: + return ModelFamily.llama3_1 + elif model_id in [ + CoreModelId.llama3_2_1b, + CoreModelId.llama3_2_3b, + CoreModelId.llama3_2_1b_instruct, + CoreModelId.llama3_2_3b_instruct, + CoreModelId.llama3_2_11b_vision, + CoreModelId.llama3_2_90b_vision, + CoreModelId.llama3_2_11b_vision_instruct, + CoreModelId.llama3_2_90b_vision_instruct, + ]: + return ModelFamily.llama3_2 + elif model_id in [ + CoreModelId.llama3_3_70b_instruct, + ]: + return ModelFamily.llama3_3 + elif model_id in [ + CoreModelId.llama4_scout_17b_16e, + CoreModelId.llama4_scout_17b_16e_instruct, + CoreModelId.llama4_maverick_17b_128e, + CoreModelId.llama4_maverick_17b_128e_instruct, + ]: + return ModelFamily.llama4 + elif model_id in [ + CoreModelId.llama_guard_3_8b, + CoreModelId.llama_guard_2_8b, + CoreModelId.llama_guard_3_11b_vision, + CoreModelId.llama_guard_3_1b, + ]: + return ModelFamily.safety + else: + raise ValueError(f"Unknown model family for {model_id}") + + +class Model(BaseModel): + core_model_id: CoreModelId + description: str + huggingface_repo: Optional[str] = None + arch_args: Dict[str, Any] + variant: str = "" + + quantization_format: CheckpointQuantizationFormat = CheckpointQuantizationFormat.bf16 + pth_file_count: int + metadata: Dict[str, Any] = Field(default_factory=dict) + + # silence pydantic until we remove the `model_` fields + model_config = ConfigDict(protected_namespaces=()) + + @property + def model_family(self) -> ModelFamily: + return model_family(self.core_model_id) + + # The SKU is uniquely identified by (model_id, variant) combo + def descriptor(self, shorten_default_variant: bool = True) -> str: + if not self.variant: + return self.core_model_id.value + return f"{self.core_model_id.value}:{self.variant}" + + @property + def is_instruct_model(self) -> bool: + return "instruct" in self.core_model_id.value + + # Featured models are shown in the non-exhaustive model list + @property + def is_featured(self) -> bool: + return self.model_family in [ + ModelFamily.llama3_1, + ModelFamily.llama3_2, + ModelFamily.llama3_3, + ModelFamily.llama4, + ModelFamily.safety, + ] + + @property + def max_seq_length(self) -> int: + if self.model_family == ModelFamily.llama2: + return 4096 + elif self.core_model_id == CoreModelId.llama_guard_2_8b: + return 4096 + elif self.model_family == ModelFamily.llama3: + return 8192 + elif self.model_family in [ModelFamily.llama3_1, ModelFamily.llama3_3]: + return 131072 + elif self.model_family == ModelFamily.llama3_2: + if self.quantization_format == CheckpointQuantizationFormat.int4: + return 8192 + return 131072 + elif self.model_family == ModelFamily.llama4: + if self.core_model_id in { + CoreModelId.llama4_scout_17b_16e, + CoreModelId.llama4_maverick_17b_128e, + }: + return 262144 + if self.core_model_id == CoreModelId.llama4_scout_17b_16e_instruct: + return 10485760 + if self.core_model_id == CoreModelId.llama4_maverick_17b_128e_instruct: + return 1048576 + + raise AssertionError(f"Unexpected core model id: {self.core_model_id}") + elif self.core_model_id in [ + CoreModelId.llama_guard_3_8b, + CoreModelId.llama_guard_3_11b_vision, + CoreModelId.llama_guard_3_1b, + ]: + return 131072 + else: + raise ValueError(f"Unknown max_seq_len for {self.core_model_id}") diff --git a/llama_stack/providers/inline/agents/meta_reference/agent_instance.py b/llama_stack/providers/inline/agents/meta_reference/agent_instance.py index e1af4ab71..6840da89f 100644 --- a/llama_stack/providers/inline/agents/meta_reference/agent_instance.py +++ b/llama_stack/providers/inline/agents/meta_reference/agent_instance.py @@ -52,6 +52,7 @@ from llama_stack.apis.inference import ( StopReason, SystemMessage, ToolDefinition, + ToolParamDefinition, ToolResponse, ToolResponseMessage, UserMessage, @@ -63,7 +64,6 @@ from llama_stack.log import get_logger from llama_stack.models.llama.datatypes import ( BuiltinTool, ToolCall, - ToolParamDefinition, ) from llama_stack.providers.utils.kvstore import KVStore from llama_stack.providers.utils.telemetry import tracing diff --git a/llama_stack/providers/inline/inference/meta_reference/__init__.py b/llama_stack/providers/inline/inference/meta_reference/__init__.py index 3ef7cfd45..3710766e2 100644 --- a/llama_stack/providers/inline/inference/meta_reference/__init__.py +++ b/llama_stack/providers/inline/inference/meta_reference/__init__.py @@ -4,13 +4,13 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -from typing import Any, Dict, Union +from typing import Any, Dict -from .config import MetaReferenceInferenceConfig, MetaReferenceQuantizedInferenceConfig +from .config import MetaReferenceInferenceConfig async def get_provider_impl( - config: Union[MetaReferenceInferenceConfig, MetaReferenceQuantizedInferenceConfig], + config: MetaReferenceInferenceConfig, _deps: Dict[str, Any], ): from .inference import MetaReferenceInferenceImpl diff --git a/llama_stack/providers/inline/inference/meta_reference/common.py b/llama_stack/providers/inline/inference/meta_reference/common.py index 3dc5e89f9..beb0d39d4 100644 --- a/llama_stack/providers/inline/inference/meta_reference/common.py +++ b/llama_stack/providers/inline/inference/meta_reference/common.py @@ -5,19 +5,10 @@ # the root directory of this source tree. from pathlib import Path -from typing import List, Optional - -from pydantic import BaseModel from llama_stack.distribution.utils.model_utils import model_local_dir -class TokenResult(BaseModel): - token: int - text: str - logprobs: Optional[List[float]] = None - - def model_checkpoint_dir(model_id) -> str: checkpoint_dir = Path(model_local_dir(model_id)) diff --git a/llama_stack/providers/inline/inference/meta_reference/config.py b/llama_stack/providers/inline/inference/meta_reference/config.py index 9e5f7747e..315667506 100644 --- a/llama_stack/providers/inline/inference/meta_reference/config.py +++ b/llama_stack/providers/inline/inference/meta_reference/config.py @@ -21,6 +21,7 @@ class MetaReferenceInferenceConfig(BaseModel): torch_seed: Optional[int] = None max_seq_len: int = 4096 max_batch_size: int = 1 + model_parallel_size: Optional[int] = None # when this is False, we assume that the distributed process group is setup by someone # outside of this code (e.g., when run inside `torchrun`). that is useful for clients @@ -31,6 +32,8 @@ class MetaReferenceInferenceConfig(BaseModel): # can override by specifying the directory explicitly checkpoint_dir: Optional[str] = None + quantization: Optional[QuantizationConfig] = None + @field_validator("model") @classmethod def validate_model(cls, model: str) -> str: @@ -47,27 +50,16 @@ class MetaReferenceInferenceConfig(BaseModel): cls, model: str = "Llama3.2-3B-Instruct", checkpoint_dir: str = "${env.CHECKPOINT_DIR:null}", + quantization_type: str = "${env.QUANTIZATION_TYPE:bf16}", + model_parallel_size: str = "${env.MODEL_PARALLEL_SIZE:0}", **kwargs, ) -> Dict[str, Any]: return { "model": model, "max_seq_len": 4096, "checkpoint_dir": checkpoint_dir, + "quantization": { + "type": quantization_type, + }, + "model_parallel_size": model_parallel_size, } - - -class MetaReferenceQuantizedInferenceConfig(MetaReferenceInferenceConfig): - quantization: QuantizationConfig - - @classmethod - def sample_run_config( - cls, - model: str = "Llama3.2-3B-Instruct", - checkpoint_dir: str = "${env.CHECKPOINT_DIR:null}", - **kwargs, - ) -> Dict[str, Any]: - config = super().sample_run_config(model, checkpoint_dir, **kwargs) - config["quantization"] = { - "type": "fp8", - } - return config diff --git a/llama_stack/providers/inline/inference/meta_reference/generators.py b/llama_stack/providers/inline/inference/meta_reference/generators.py index 4b0ed7ecd..65bed4d8c 100644 --- a/llama_stack/providers/inline/inference/meta_reference/generators.py +++ b/llama_stack/providers/inline/inference/meta_reference/generators.py @@ -11,19 +11,18 @@ import torch from lmformatenforcer import JsonSchemaParser, TokenEnforcer, TokenEnforcerTokenizerData from llama_stack.apis.inference import ( - Fp8QuantizationConfig, - Int4QuantizationConfig, + GreedySamplingStrategy, JsonSchemaResponseFormat, ResponseFormat, -) -from llama_stack.models.llama.datatypes import ( - GreedySamplingStrategy, - Model, SamplingParams, TopPSamplingStrategy, ) +from llama_stack.models.llama.datatypes import QuantizationMode +from llama_stack.models.llama.llama3.generation import Llama3 from llama_stack.models.llama.llama3.tokenizer import Tokenizer as Llama3Tokenizer +from llama_stack.models.llama.llama4.generation import Llama4 from llama_stack.models.llama.llama4.tokenizer import Tokenizer as Llama4Tokenizer +from llama_stack.models.llama.sku_types import Model from llama_stack.providers.utils.inference.prompt_adapter import ( ChatCompletionRequestWithRawContent, CompletionRequestWithRawContent, @@ -31,10 +30,8 @@ from llama_stack.providers.utils.inference.prompt_adapter import ( ) from .common import model_checkpoint_dir -from .config import MetaReferenceInferenceConfig, MetaReferenceQuantizedInferenceConfig +from .config import MetaReferenceInferenceConfig from .inference import resolve_model -from .llama3.generation import Llama3 -from .llama4.generation import Llama4 Tokenizer = Llama4Tokenizer | Llama3Tokenizer @@ -116,10 +113,11 @@ def _infer_tool_prompt_format(request: ChatCompletionRequestWithRawContent): return get_default_tool_prompt_format(request.model) +# TODO: combine Llama3 and Llama4 generators since they are almost identical now class Llama4Generator: def __init__( self, - config: MetaReferenceInferenceConfig | MetaReferenceQuantizedInferenceConfig, + config: MetaReferenceInferenceConfig, model_id: str, llama_model: Model, ): @@ -134,11 +132,13 @@ class Llama4Generator: # if the model is a native llama model, get the default checkpoint_dir based on model core_model_id value ckpt_dir = model_checkpoint_dir(resolved_model.descriptor()) - if isinstance(config, MetaReferenceQuantizedInferenceConfig): - if isinstance(config.quantization, Fp8QuantizationConfig): - quantization_mode = "fp8_mixed" - elif isinstance(config.quantization, Int4QuantizationConfig): - quantization_mode = "int4_mixed" + if config.quantization: + if config.quantization.type == "fp8_mixed": + quantization_mode = QuantizationMode.fp8_mixed + elif config.quantization.type == "int4_mixed": + quantization_mode = QuantizationMode.int4_mixed + elif config.quantization.type == "bf16": + quantization_mode = None else: raise ValueError(f"Unsupported quantization mode {config.quantization}") else: @@ -148,7 +148,7 @@ class Llama4Generator: ckpt_dir=ckpt_dir, max_seq_len=config.max_seq_len, max_batch_size=config.max_batch_size, - world_size=llama_model.pth_file_count, + world_size=config.model_parallel_size or llama_model.pth_file_count, quantization_mode=quantization_mode, ) @@ -166,8 +166,8 @@ class Llama4Generator: max_gen_len = self.args.max_seq_len - 1 temperature, top_p = _infer_sampling_params(sampling_params) - yield from self.inner_generator.generate( - llm_input=self.formatter.encode_content(request.content), + for result in self.inner_generator.generate( + llm_inputs=[self.formatter.encode_content(request.content)], max_gen_len=max_gen_len, temperature=temperature, top_p=top_p, @@ -178,7 +178,8 @@ class Llama4Generator: self.args.vocab_size, request.response_format, ), - ) + ): + yield result[0] def chat_completion( self, @@ -190,8 +191,8 @@ class Llama4Generator: max_gen_len = self.args.max_seq_len - 1 temperature, top_p = _infer_sampling_params(sampling_params) - yield from self.inner_generator.generate( - llm_input=self.formatter.encode_dialog_prompt(request.messages, _infer_tool_prompt_format(request)), + for result in self.inner_generator.generate( + llm_inputs=[self.formatter.encode_dialog_prompt(request.messages, _infer_tool_prompt_format(request))], max_gen_len=max_gen_len, temperature=temperature, top_p=top_p, @@ -202,20 +203,46 @@ class Llama4Generator: self.args.vocab_size, request.response_format, ), - ) + ): + yield result[0] class Llama3Generator: def __init__( self, - config: MetaReferenceInferenceConfig | MetaReferenceQuantizedInferenceConfig, + config: MetaReferenceInferenceConfig, model_id: str, llama_model: Model, ): + if config.checkpoint_dir and config.checkpoint_dir != "null": + ckpt_dir = config.checkpoint_dir + else: + resolved_model = resolve_model(model_id) + if resolved_model is None: + # if the model is not a native llama model, get the default checkpoint_dir based on model id + ckpt_dir = model_checkpoint_dir(model_id) + else: + # if the model is a native llama model, get the default checkpoint_dir based on model core_model_id value + ckpt_dir = model_checkpoint_dir(resolved_model.descriptor()) + + if config.quantization: + if config.quantization.type == "fp8_mixed": + quantization_mode = QuantizationMode.fp8_mixed + elif config.quantization.type == "int4_mixed": + quantization_mode = QuantizationMode.int4_mixed + elif config.quantization.type == "bf16": + quantization_mode = None + else: + raise ValueError(f"Unsupported quantization mode {config.quantization}") + else: + quantization_mode = None + self.inner_generator = Llama3.build( - config=config, - model_id=model_id, - llama_model=llama_model, + ckpt_dir=ckpt_dir, + max_seq_len=config.max_seq_len, + max_batch_size=config.max_batch_size, + world_size=config.model_parallel_size or llama_model.pth_file_count, + quantization_mode=quantization_mode, ) self.tokenizer = self.inner_generator.tokenizer self.args = self.inner_generator.args @@ -231,8 +258,8 @@ class Llama3Generator: max_gen_len = self.args.max_seq_len - 1 temperature, top_p = _infer_sampling_params(sampling_params) - yield from self.inner_generator.generate( - model_input=self.formatter.encode_content(request.content), + for result in self.inner_generator.generate( + llm_inputs=[self.formatter.encode_content(request.content)], max_gen_len=max_gen_len, temperature=temperature, top_p=top_p, @@ -243,7 +270,8 @@ class Llama3Generator: self.args.vocab_size, request.response_format, ), - ) + ): + yield result[0] def chat_completion( self, @@ -255,8 +283,8 @@ class Llama3Generator: max_gen_len = self.args.max_seq_len - 1 temperature, top_p = _infer_sampling_params(sampling_params) - yield from self.inner_generator.generate( - model_input=self.formatter.encode_dialog_prompt(request.messages, _infer_tool_prompt_format(request)), + for result in self.inner_generator.generate( + llm_inputs=[self.formatter.encode_dialog_prompt(request.messages, _infer_tool_prompt_format(request))], max_gen_len=max_gen_len, temperature=temperature, top_p=top_p, @@ -267,4 +295,5 @@ class Llama3Generator: self.args.vocab_size, request.response_format, ), - ) + ): + yield result[0] diff --git a/llama_stack/providers/inline/inference/meta_reference/inference.py b/llama_stack/providers/inline/inference/meta_reference/inference.py index da217728b..5f81d6421 100644 --- a/llama_stack/providers/inline/inference/meta_reference/inference.py +++ b/llama_stack/providers/inline/inference/meta_reference/inference.py @@ -31,23 +31,21 @@ from llama_stack.apis.inference import ( LogProbConfig, Message, ResponseFormat, + SamplingParams, + StopReason, TokenLogProbs, ToolChoice, ToolConfig, -) -from llama_stack.apis.models import Model, ModelType -from llama_stack.models.llama.datatypes import ( - ModelFamily, - SamplingParams, - StopReason, ToolDefinition, ToolPromptFormat, ) +from llama_stack.apis.models import Model, ModelType from llama_stack.models.llama.llama3.chat_format import ChatFormat as Llama3ChatFormat from llama_stack.models.llama.llama3.tokenizer import Tokenizer as Llama3Tokenizer from llama_stack.models.llama.llama4.chat_format import ChatFormat as Llama4ChatFormat from llama_stack.models.llama.llama4.tokenizer import Tokenizer as Llama4Tokenizer from llama_stack.models.llama.sku_list import resolve_model +from llama_stack.models.llama.sku_types import ModelFamily from llama_stack.providers.datatypes import ModelsProtocolPrivate from llama_stack.providers.utils.inference.embedding_mixin import ( SentenceTransformerEmbeddingMixin, @@ -151,7 +149,7 @@ class MetaReferenceInferenceImpl( if self.config.create_distributed_process_group: self.generator = LlamaModelParallelGenerator( - model_parallel_size=llama_model.pth_file_count, + model_parallel_size=self.config.model_parallel_size or llama_model.pth_file_count, builder_fn=builder_fn, builder_params=builder_params, formatter=( diff --git a/llama_stack/providers/inline/inference/meta_reference/llama3/generation.py b/llama_stack/providers/inline/inference/meta_reference/llama3/generation.py deleted file mode 100644 index 3805e4310..000000000 --- a/llama_stack/providers/inline/inference/meta_reference/llama3/generation.py +++ /dev/null @@ -1,346 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the terms described in the LICENSE file in -# the root directory of this source tree. - - -import json -import os -import sys -import time -from pathlib import Path -from typing import Callable, Generator, Optional, Union - -import torch -import torch.nn.functional as F -from fairscale.nn.model_parallel.initialize import ( - get_model_parallel_rank, - initialize_model_parallel, - model_parallel_is_initialized, -) - -from llama_stack.apis.inference import ( - Fp8QuantizationConfig, - Int4QuantizationConfig, -) -from llama_stack.log import get_logger -from llama_stack.models.llama.datatypes import Model -from llama_stack.models.llama.llama3.chat_format import ChatFormat, LLMInput -from llama_stack.models.llama.llama3.tokenizer import Tokenizer -from llama_stack.models.llama.sku_list import resolve_model - -from ..common import TokenResult, model_checkpoint_dir -from ..config import MetaReferenceInferenceConfig, MetaReferenceQuantizedInferenceConfig -from .args import ModelArgs -from .model import Transformer -from .multimodal.model import CrossAttentionTransformer - -log = get_logger(__name__, category="inference") - - -class Llama3: - @staticmethod - def build( - config: Union[MetaReferenceInferenceConfig, MetaReferenceQuantizedInferenceConfig], - model_id: str, - llama_model: Model, - ): - """ - Build a Llama instance by initializing and loading a model checkpoint. - - Note: - This method initializes the distributed process group, sets the device to CUDA, - and loads the pre-trained model and tokenizer. - """ - if "DEVICE" in os.environ: - device = os.environ.get("DEVICE") - if device == "cuda": - assert torch.cuda.is_available(), "PyTorch CUDA backend not available" - if device == "xpu": - assert torch.xpu.is_available(), "PyTorch XPU backend not available" - else: - if torch.cuda.is_available(): - device = "cuda" - elif torch.xpu.is_available(): - device = "xpu" - else: - device = "cpu" - log.info(f"Using {device} device") - - llama_model_id = llama_model.core_model_id.value - if not torch.distributed.is_initialized(): - if device == "cuda": - torch.distributed.init_process_group("nccl") - else: - torch.distributed.init_process_group("gloo") - - model_parallel_size = llama_model.pth_file_count - - if not model_parallel_is_initialized(): - initialize_model_parallel(model_parallel_size) - - local_rank = int(os.environ.get("LOCAL_RANK", 0)) - if device == "cuda": - torch.cuda.set_device(local_rank) - elif device == "xpu": - torch.xpu.set_device(local_rank) - - # seed must be the same in all processes - if config.torch_seed is not None: - torch.manual_seed(config.torch_seed) - - if local_rank > 0: - sys.stdout = open(os.devnull, "w") - - start_time = time.time() - if config.checkpoint_dir and config.checkpoint_dir != "null": - ckpt_dir = config.checkpoint_dir - else: - resolved_model = resolve_model(model_id) - if resolved_model is None: - # if the model is not a native llama model, get the default checkpoint_dir based on model id - ckpt_dir = model_checkpoint_dir(model_id) - else: - # if the model is a native llama model, get the default checkpoint_dir based on model core_model_id value - ckpt_dir = model_checkpoint_dir(resolved_model.descriptor()) - - checkpoints = sorted(Path(ckpt_dir).glob("*.pth")) - assert len(checkpoints) > 0, f"no checkpoint files found in {ckpt_dir}" - assert model_parallel_size == len(checkpoints), ( - f"Loading a checkpoint for MP={len(checkpoints)} but world size is {model_parallel_size}" - ) - ckpt_path = checkpoints[get_model_parallel_rank()] - state_dict = torch.load(ckpt_path, map_location="cpu", weights_only=True) - with open(Path(ckpt_dir) / "params.json", "r") as f: - params = json.loads(f.read()) - - if "model" in params: - params = params["model"] - - model_args: ModelArgs = ModelArgs( - max_seq_len=config.max_seq_len, - max_batch_size=config.max_batch_size, - **params, - ) - - tokenizer = Tokenizer.get_instance() - assert model_args.vocab_size == tokenizer.n_words, ( - f"model_args vocab = {model_args.vocab_size} but tokenizer vocab = {tokenizer.n_words}" - ) - - if isinstance(config, MetaReferenceQuantizedInferenceConfig): - if isinstance(config.quantization, Fp8QuantizationConfig): - from .quantization.loader import convert_to_fp8_quantized_model - - # load on CPU in bf16 so that fp8 conversion does not find an - # unexpected (fp32, e.g.) datatype - torch.set_default_tensor_type(torch.BFloat16Tensor) - if model_args.vision_chunk_size > 0: - model = CrossAttentionTransformer(model_args) - model.setup_cache(model_args.max_batch_size, torch.bfloat16) - else: - model = Transformer(model_args) - model.load_state_dict(state_dict, strict=False) - model = convert_to_fp8_quantized_model(model, config, ckpt_dir) - elif isinstance(config.quantization, Int4QuantizationConfig): - from .quantization.loader import convert_to_int4_quantized_model - - model = Transformer(model_args) - model = convert_to_int4_quantized_model(model, model_args, config) - model.load_state_dict(state_dict, strict=True) - - if model_args.quantization_args is not None and model_args.quantization_args.spinquant: - # Add a wrapper for adding hadamard transform for spinquant. - # This needs to be done after loading the state dict otherwise an error will be raised while - # loading the state dict. - from ..hadamard_utils import ( - add_hadamard_transform_for_spinquant, - ) - - add_hadamard_transform_for_spinquant(model) - else: - raise NotImplementedError("Currently int4 and fp8 are the only supported quantization methods.") - else: - if device == "cuda": - if torch.cuda.is_bf16_supported(): - torch.set_default_tensor_type(torch.cuda.BFloat16Tensor) - else: - torch.set_default_tensor_type(torch.cuda.HalfTensor) - else: - torch.set_default_device(device) - if device == "xpu" and torch.xpu.is_bf16_supported(): - torch.set_default_dtype(torch.bfloat16) - else: - torch.set_default_dtype(torch.half) - if model_args.vision_chunk_size > 0: - model = CrossAttentionTransformer(model_args) - model.setup_cache(model_args.max_batch_size, torch.bfloat16) - else: - model = Transformer(model_args) - model.load_state_dict(state_dict, strict=False) - - model.to(device) - - log.info(f"Loaded in {time.time() - start_time:.2f} seconds") - return Llama3(model, tokenizer, model_args, llama_model_id) - - def __init__( - self, - model: Transformer, - tokenizer: Tokenizer, - args: ModelArgs, - llama_model: str, - ): - self.args = args - self.model = model - self.tokenizer = tokenizer - self.formatter = ChatFormat(tokenizer) - self.llama_model = llama_model - - @torch.inference_mode() - def generate( - self, - model_input: LLMInput, - max_gen_len: int, - temperature: float = 0.6, - top_p: float = 0.9, - logprobs: bool = False, - echo: bool = False, - print_input_tokens: bool = False, - logits_processor: Optional[Callable[[torch.Tensor, torch.Tensor], torch.Tensor]] = None, - ) -> Generator: - params = self.model.params - - if print_input_tokens: - input_tokens = [self.formatter.vision_token if t == 128256 else t for t in model_input.tokens] - log.info("Input to model -> " + self.tokenizer.decode(input_tokens)) - prompt_tokens = [model_input.tokens] - - bsz = 1 - assert bsz <= params.max_batch_size, (bsz, params.max_batch_size) - - min_prompt_len = min(len(t) for t in prompt_tokens) - max_prompt_len = max(len(t) for t in prompt_tokens) - - if max_prompt_len >= params.max_seq_len: - log.error(f"Out of token budget {max_prompt_len} vs {params.max_seq_len}") - return - - total_len = min(max_gen_len + max_prompt_len, params.max_seq_len) - - is_vision = isinstance(self.model, CrossAttentionTransformer) - if is_vision: - images = model_input.vision.images if model_input.vision is not None else [] - mask = model_input.vision.mask if model_input.vision is not None else [] - - # the method works for bsz > 1 so add a batch dimension - xattn_caches, cross_attention_masks, full_text_row_masked_out_mask = self.model.compute_vision_tokens_masks( - batch_images=[images], - batch_masks=[mask], - total_len=total_len, - ) - - pad_id = self.tokenizer.pad_id - tokens = torch.full((bsz, total_len), pad_id, dtype=torch.long) - for k, t in enumerate(prompt_tokens): - tokens[k, : len(t)] = torch.tensor(t, dtype=torch.long) - if logprobs: - token_logprobs = torch.zeros_like(tokens) - - prev_pos = 0 - eos_reached = torch.tensor([False] * bsz) - input_text_mask = tokens != pad_id - if min_prompt_len == total_len: - # TODO(ashwin): unify this branch with the one below and figure out multimodal crap - logits = self.model.forward(tokens, prev_pos) - token_logprobs = -F.cross_entropy( - input=logits.transpose(1, 2), - target=tokens, - reduction="none", - ignore_index=pad_id, - ) - - stop_tokens = torch.tensor(self.tokenizer.stop_tokens) - for cur_pos in range(min_prompt_len, total_len): - if is_vision: - position_ids = torch.arange(prev_pos, cur_pos, dtype=torch.long) - logits = self.model.forward( - position_ids, - tokens, - cross_attention_masks, - full_text_row_masked_out_mask, - xattn_caches, - ) - else: - logits = self.model.forward(tokens[:, prev_pos:cur_pos], prev_pos) - - if logits_processor is not None: - logits = logits_processor(tokens[:, :cur_pos], logits) - - if temperature > 0: - probs = torch.softmax(logits[:, -1] / temperature, dim=-1) - next_token = sample_top_p(probs, top_p) - else: - next_token = torch.argmax(logits[:, -1], dim=-1) - - next_token = next_token.reshape(-1) - # only replace token if prompt has already been generated - next_token = torch.where(input_text_mask[:, cur_pos], tokens[:, cur_pos], next_token) - tokens[:, cur_pos] = next_token - - target = tokens[:, prev_pos + 1 : cur_pos + 1] - if is_vision: - # the logits space (num_classes) is designed to never contain a media_token - # however our input token stream does contain them. we need to nuke them here - # or else the CUDA kernels will crash with an illegal memory access - vision_tokens = [self.tokenizer.special_tokens["<|image|>"], 128256] - masks = [target.eq(t) for t in vision_tokens] - if len(masks) > 1: - mask = torch.logical_or(*masks) - else: - mask = masks[0] - target[mask] = 0 - - if logprobs: - token_logprobs[:, prev_pos + 1 : cur_pos + 1] = -F.cross_entropy( - input=logits.transpose(1, 2), - target=tokens[:, prev_pos + 1 : cur_pos + 1], - reduction="none", - ignore_index=pad_id, - ) - eos_reached |= (~input_text_mask[:, cur_pos]) & (torch.isin(next_token, stop_tokens)) - yield TokenResult( - token=next_token[0].item(), - text=self.tokenizer.decode(next_token.tolist()), - logprobs=(token_logprobs[:, cur_pos : cur_pos + 1][0].tolist() if logprobs else None), - ) - - prev_pos = cur_pos - if all(eos_reached): - break - - -def sample_top_p(probs, p): - """ - Perform top-p (nucleus) sampling on a probability distribution. - - Args: - probs (torch.Tensor): Probability distribution tensor. - p (float): Probability threshold for top-p sampling. - - Returns: - torch.Tensor: Sampled token indices. - - Note: - Top-p sampling selects the smallest set of tokens whose cumulative probability mass - exceeds the threshold p. The distribution is renormalized based on the selected tokens. - """ - probs_sort, probs_idx = torch.sort(probs, dim=-1, descending=True) - probs_sum = torch.cumsum(probs_sort, dim=-1) - mask = probs_sum - probs_sort > p - probs_sort[mask] = 0.0 - probs_sort.div_(probs_sort.sum(dim=-1, keepdim=True)) - next_token = torch.multinomial(probs_sort, num_samples=1) - next_token = torch.gather(probs_idx, -1, next_token) - return next_token diff --git a/llama_stack/providers/inline/inference/meta_reference/parallel_utils.py b/llama_stack/providers/inline/inference/meta_reference/parallel_utils.py index e8767c2ff..74fc49d5e 100644 --- a/llama_stack/providers/inline/inference/meta_reference/parallel_utils.py +++ b/llama_stack/providers/inline/inference/meta_reference/parallel_utils.py @@ -32,13 +32,12 @@ from pydantic import BaseModel, Field from torch.distributed.launcher.api import LaunchConfig, elastic_launch from typing_extensions import Annotated +from llama_stack.models.llama.datatypes import GenerationResult from llama_stack.providers.utils.inference.prompt_adapter import ( ChatCompletionRequestWithRawContent, CompletionRequestWithRawContent, ) -from .common import TokenResult - log = logging.getLogger(__name__) @@ -75,7 +74,7 @@ class TaskRequest(BaseModel): class TaskResponse(BaseModel): type: Literal[ProcessingMessageName.task_response] = ProcessingMessageName.task_response - result: TokenResult + result: GenerationResult class ExceptionResponse(BaseModel): diff --git a/llama_stack/providers/inline/inference/vllm/openai_utils.py b/llama_stack/providers/inline/inference/vllm/openai_utils.py index 90b5398f9..d34f5ad5f 100644 --- a/llama_stack/providers/inline/inference/vllm/openai_utils.py +++ b/llama_stack/providers/inline/inference/vllm/openai_utils.py @@ -14,9 +14,10 @@ from llama_stack.apis.inference import ( JsonSchemaResponseFormat, Message, ToolChoice, + ToolDefinition, UserMessage, ) -from llama_stack.models.llama.datatypes import BuiltinTool, ToolDefinition +from llama_stack.models.llama.datatypes import BuiltinTool from llama_stack.providers.utils.inference.openai_compat import ( convert_message_to_openai_dict, get_sampling_options, diff --git a/llama_stack/providers/inline/inference/vllm/vllm.py b/llama_stack/providers/inline/inference/vllm/vllm.py index 256e0f821..ea2643b7a 100644 --- a/llama_stack/providers/inline/inference/vllm/vllm.py +++ b/llama_stack/providers/inline/inference/vllm/vllm.py @@ -46,6 +46,8 @@ from llama_stack.apis.inference import ( TokenLogProbs, ToolChoice, ToolConfig, + TopKSamplingStrategy, + TopPSamplingStrategy, ) from llama_stack.apis.models import Model from llama_stack.log import get_logger @@ -55,8 +57,6 @@ from llama_stack.models.llama.datatypes import ( ToolCall, ToolDefinition, ToolPromptFormat, - TopKSamplingStrategy, - TopPSamplingStrategy, ) from llama_stack.models.llama.llama3.chat_format import ChatFormat from llama_stack.models.llama.llama3.tokenizer import Tokenizer diff --git a/llama_stack/providers/inline/post_training/torchtune/common/utils.py b/llama_stack/providers/inline/post_training/torchtune/common/utils.py index f8a1c0436..a040ca1b0 100644 --- a/llama_stack/providers/inline/post_training/torchtune/common/utils.py +++ b/llama_stack/providers/inline/post_training/torchtune/common/utils.py @@ -22,8 +22,8 @@ from torchtune.models.llama3_2 import lora_llama3_2_3b from torchtune.modules.transforms import Transform from llama_stack.apis.post_training import DatasetFormat -from llama_stack.models.llama.datatypes import Model from llama_stack.models.llama.sku_list import resolve_model +from llama_stack.models.llama.sku_types import Model BuildLoraModelCallable = Callable[..., torch.nn.Module] BuildTokenizerCallable = Callable[..., Llama3Tokenizer] diff --git a/llama_stack/providers/inline/safety/llama_guard/llama_guard.py b/llama_stack/providers/inline/safety/llama_guard/llama_guard.py index e514e3781..d95c40976 100644 --- a/llama_stack/providers/inline/safety/llama_guard/llama_guard.py +++ b/llama_stack/providers/inline/safety/llama_guard/llama_guard.py @@ -23,7 +23,8 @@ from llama_stack.apis.safety import ( ) from llama_stack.apis.shields import Shield from llama_stack.distribution.datatypes import Api -from llama_stack.models.llama.datatypes import CoreModelId, Role +from llama_stack.models.llama.datatypes import Role +from llama_stack.models.llama.sku_types import CoreModelId from llama_stack.providers.datatypes import ShieldsProtocolPrivate from llama_stack.providers.utils.inference.prompt_adapter import ( interleaved_content_as_str, diff --git a/llama_stack/providers/registry/inference.py b/llama_stack/providers/registry/inference.py index 51ea4cbef..5f9ae421f 100644 --- a/llama_stack/providers/registry/inference.py +++ b/llama_stack/providers/registry/inference.py @@ -24,6 +24,8 @@ META_REFERENCE_DEPS = [ "zmq", "lm-format-enforcer", "sentence-transformers", + "torchao==0.5.0", + "fbgemm-gpu-genai==1.1.2", ] @@ -36,13 +38,6 @@ def available_providers() -> List[ProviderSpec]: module="llama_stack.providers.inline.inference.meta_reference", config_class="llama_stack.providers.inline.inference.meta_reference.MetaReferenceInferenceConfig", ), - InlineProviderSpec( - api=Api.inference, - provider_type="inline::meta-reference-quantized", - pip_packages=META_REFERENCE_DEPS + ["fbgemm-gpu", "torchao==0.5.0"], - module="llama_stack.providers.inline.inference.meta_reference", - config_class="llama_stack.providers.inline.inference.meta_reference.MetaReferenceQuantizedInferenceConfig", - ), InlineProviderSpec( api=Api.inference, provider_type="inline::vllm", diff --git a/llama_stack/providers/remote/inference/bedrock/models.py b/llama_stack/providers/remote/inference/bedrock/models.py index c5079799f..ec8120049 100644 --- a/llama_stack/providers/remote/inference/bedrock/models.py +++ b/llama_stack/providers/remote/inference/bedrock/models.py @@ -4,7 +4,7 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -from llama_stack.models.llama.datatypes import CoreModelId +from llama_stack.models.llama.sku_types import CoreModelId from llama_stack.providers.utils.inference.model_registry import ( build_hf_repo_model_entry, ) diff --git a/llama_stack/providers/remote/inference/cerebras/cerebras.py b/llama_stack/providers/remote/inference/cerebras/cerebras.py index a53e6e5a5..43d986b86 100644 --- a/llama_stack/providers/remote/inference/cerebras/cerebras.py +++ b/llama_stack/providers/remote/inference/cerebras/cerebras.py @@ -28,8 +28,8 @@ from llama_stack.apis.inference import ( ToolConfig, ToolDefinition, ToolPromptFormat, + TopKSamplingStrategy, ) -from llama_stack.models.llama.datatypes import TopKSamplingStrategy from llama_stack.providers.utils.inference.model_registry import ( ModelRegistryHelper, ) diff --git a/llama_stack/providers/remote/inference/cerebras/models.py b/llama_stack/providers/remote/inference/cerebras/models.py index 37419bf4c..38301b32a 100644 --- a/llama_stack/providers/remote/inference/cerebras/models.py +++ b/llama_stack/providers/remote/inference/cerebras/models.py @@ -4,7 +4,7 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -from llama_stack.models.llama.datatypes import CoreModelId +from llama_stack.models.llama.sku_types import CoreModelId from llama_stack.providers.utils.inference.model_registry import ( build_hf_repo_model_entry, ) diff --git a/llama_stack/providers/remote/inference/databricks/databricks.py b/llama_stack/providers/remote/inference/databricks/databricks.py index 53a9c04f4..0eaf0135b 100644 --- a/llama_stack/providers/remote/inference/databricks/databricks.py +++ b/llama_stack/providers/remote/inference/databricks/databricks.py @@ -28,7 +28,7 @@ from llama_stack.apis.inference import ( ToolDefinition, ToolPromptFormat, ) -from llama_stack.models.llama.datatypes import CoreModelId +from llama_stack.models.llama.sku_types import CoreModelId from llama_stack.providers.utils.inference.model_registry import ( ModelRegistryHelper, build_hf_repo_model_entry, diff --git a/llama_stack/providers/remote/inference/fireworks/models.py b/llama_stack/providers/remote/inference/fireworks/models.py index a0dc11768..4975d061f 100644 --- a/llama_stack/providers/remote/inference/fireworks/models.py +++ b/llama_stack/providers/remote/inference/fireworks/models.py @@ -5,7 +5,7 @@ # the root directory of this source tree. from llama_stack.apis.models.models import ModelType -from llama_stack.models.llama.datatypes import CoreModelId +from llama_stack.models.llama.sku_types import CoreModelId from llama_stack.providers.utils.inference.model_registry import ( ProviderModelEntry, build_hf_repo_model_entry, diff --git a/llama_stack/providers/remote/inference/nvidia/models.py b/llama_stack/providers/remote/inference/nvidia/models.py index 879855003..964125148 100644 --- a/llama_stack/providers/remote/inference/nvidia/models.py +++ b/llama_stack/providers/remote/inference/nvidia/models.py @@ -5,7 +5,7 @@ # the root directory of this source tree. from llama_stack.apis.models import ModelType -from llama_stack.models.llama.datatypes import CoreModelId +from llama_stack.models.llama.sku_types import CoreModelId from llama_stack.providers.utils.inference.model_registry import ( ProviderModelEntry, build_hf_repo_model_entry, diff --git a/llama_stack/providers/remote/inference/nvidia/nvidia.py b/llama_stack/providers/remote/inference/nvidia/nvidia.py index 5caf19fda..e1f5d7a6a 100644 --- a/llama_stack/providers/remote/inference/nvidia/nvidia.py +++ b/llama_stack/providers/remote/inference/nvidia/nvidia.py @@ -29,15 +29,13 @@ from llama_stack.apis.inference import ( LogProbConfig, Message, ResponseFormat, + SamplingParams, TextTruncation, ToolChoice, ToolConfig, -) -from llama_stack.models.llama.datatypes import ( - SamplingParams, ToolDefinition, - ToolPromptFormat, ) +from llama_stack.models.llama.datatypes import ToolPromptFormat from llama_stack.providers.utils.inference.model_registry import ( ModelRegistryHelper, ) diff --git a/llama_stack/providers/remote/inference/nvidia/openai_utils.py b/llama_stack/providers/remote/inference/nvidia/openai_utils.py index 0582cb816..3f2769b26 100644 --- a/llama_stack/providers/remote/inference/nvidia/openai_utils.py +++ b/llama_stack/providers/remote/inference/nvidia/openai_utils.py @@ -19,11 +19,9 @@ from llama_stack.apis.inference import ( CompletionRequest, CompletionResponse, CompletionResponseStreamChunk, + GreedySamplingStrategy, JsonSchemaResponseFormat, TokenLogProbs, -) -from llama_stack.models.llama.datatypes import ( - GreedySamplingStrategy, TopKSamplingStrategy, TopPSamplingStrategy, ) diff --git a/llama_stack/providers/remote/inference/ollama/models.py b/llama_stack/providers/remote/inference/ollama/models.py index be556762c..42e364105 100644 --- a/llama_stack/providers/remote/inference/ollama/models.py +++ b/llama_stack/providers/remote/inference/ollama/models.py @@ -5,7 +5,7 @@ # the root directory of this source tree. from llama_stack.apis.models.models import ModelType -from llama_stack.models.llama.datatypes import CoreModelId +from llama_stack.models.llama.sku_types import CoreModelId from llama_stack.providers.utils.inference.model_registry import ( ProviderModelEntry, build_hf_repo_model_entry, diff --git a/llama_stack/providers/remote/inference/sambanova/models.py b/llama_stack/providers/remote/inference/sambanova/models.py index 2231be22d..9589ea268 100644 --- a/llama_stack/providers/remote/inference/sambanova/models.py +++ b/llama_stack/providers/remote/inference/sambanova/models.py @@ -4,7 +4,7 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -from llama_stack.models.llama.datatypes import CoreModelId +from llama_stack.models.llama.sku_types import CoreModelId from llama_stack.providers.utils.inference.model_registry import ( build_hf_repo_model_entry, ) diff --git a/llama_stack/providers/remote/inference/sambanova/sambanova.py b/llama_stack/providers/remote/inference/sambanova/sambanova.py index 635a42d38..a3badd468 100644 --- a/llama_stack/providers/remote/inference/sambanova/sambanova.py +++ b/llama_stack/providers/remote/inference/sambanova/sambanova.py @@ -21,6 +21,7 @@ from llama_stack.apis.inference import ( CompletionMessage, EmbeddingsResponse, EmbeddingTaskType, + GreedySamplingStrategy, Inference, LogProbConfig, Message, @@ -35,12 +36,9 @@ from llama_stack.apis.inference import ( ToolDefinition, ToolPromptFormat, ToolResponseMessage, - UserMessage, -) -from llama_stack.models.llama.datatypes import ( - GreedySamplingStrategy, TopKSamplingStrategy, TopPSamplingStrategy, + UserMessage, ) from llama_stack.providers.utils.inference.model_registry import ModelRegistryHelper from llama_stack.providers.utils.inference.openai_compat import ( diff --git a/llama_stack/providers/remote/inference/together/models.py b/llama_stack/providers/remote/inference/together/models.py index 63d3d94b5..f014c03f0 100644 --- a/llama_stack/providers/remote/inference/together/models.py +++ b/llama_stack/providers/remote/inference/together/models.py @@ -5,7 +5,7 @@ # the root directory of this source tree. from llama_stack.apis.models.models import ModelType -from llama_stack.models.llama.datatypes import CoreModelId +from llama_stack.models.llama.sku_types import CoreModelId from llama_stack.providers.utils.inference.model_registry import ( ProviderModelEntry, build_hf_repo_model_entry, diff --git a/llama_stack/providers/remote/post_training/nvidia/models.py b/llama_stack/providers/remote/post_training/nvidia/models.py index 04a9af38c..7c696ac20 100644 --- a/llama_stack/providers/remote/post_training/nvidia/models.py +++ b/llama_stack/providers/remote/post_training/nvidia/models.py @@ -6,7 +6,7 @@ from typing import List -from llama_stack.models.llama.datatypes import CoreModelId +from llama_stack.models.llama.sku_types import CoreModelId from llama_stack.providers.utils.inference.model_registry import ( ProviderModelEntry, build_hf_repo_model_entry, diff --git a/llama_stack/providers/tests/report.py b/llama_stack/providers/tests/report.py index c9a7f69a8..bc29534be 100644 --- a/llama_stack/providers/tests/report.py +++ b/llama_stack/providers/tests/report.py @@ -12,8 +12,8 @@ import pytest from pytest import ExitCode from pytest_html.basereport import _process_outcome -from llama_stack.models.llama.datatypes import CoreModelId from llama_stack.models.llama.sku_list import all_registered_models +from llama_stack.models.llama.sku_types import CoreModelId INFERENCE_APIS = ["chat_completion"] FUNCTIONALITIES = ["streaming", "structured_output", "tool_calling"] diff --git a/llama_stack/providers/utils/inference/__init__.py b/llama_stack/providers/utils/inference/__init__.py index a885da235..e36be9404 100644 --- a/llama_stack/providers/utils/inference/__init__.py +++ b/llama_stack/providers/utils/inference/__init__.py @@ -6,8 +6,8 @@ from typing import List -from llama_stack.models.llama.datatypes import * # noqa: F403 from llama_stack.models.llama.sku_list import all_registered_models +from llama_stack.models.llama.sku_types import * # noqa: F403 def is_supported_safety_model(model: Model) -> bool: diff --git a/llama_stack/providers/utils/inference/openai_compat.py b/llama_stack/providers/utils/inference/openai_compat.py index e475d77b6..44a89dfb0 100644 --- a/llama_stack/providers/utils/inference/openai_compat.py +++ b/llama_stack/providers/utils/inference/openai_compat.py @@ -73,21 +73,21 @@ from llama_stack.apis.inference import ( CompletionMessage, CompletionResponse, CompletionResponseStreamChunk, + GreedySamplingStrategy, Message, + SamplingParams, SystemMessage, TokenLogProbs, ToolResponseMessage, + TopKSamplingStrategy, + TopPSamplingStrategy, UserMessage, ) from llama_stack.models.llama.datatypes import ( BuiltinTool, - GreedySamplingStrategy, - SamplingParams, StopReason, ToolCall, ToolDefinition, - TopKSamplingStrategy, - TopPSamplingStrategy, ) from llama_stack.providers.utils.inference.prompt_adapter import ( convert_image_content_to_url, diff --git a/llama_stack/providers/utils/inference/prompt_adapter.py b/llama_stack/providers/utils/inference/prompt_adapter.py index 0231312cc..4f9c4927a 100644 --- a/llama_stack/providers/utils/inference/prompt_adapter.py +++ b/llama_stack/providers/utils/inference/prompt_adapter.py @@ -34,7 +34,6 @@ from llama_stack.apis.inference import ( ) from llama_stack.log import get_logger from llama_stack.models.llama.datatypes import ( - ModelFamily, RawContent, RawContentItem, RawMediaItem, @@ -43,7 +42,6 @@ from llama_stack.models.llama.datatypes import ( Role, StopReason, ToolPromptFormat, - is_multimodal, ) from llama_stack.models.llama.llama3.chat_format import ChatFormat from llama_stack.models.llama.llama3.prompt_templates import ( @@ -55,6 +53,7 @@ from llama_stack.models.llama.llama3.prompt_templates import ( ) from llama_stack.models.llama.llama3.tokenizer import Tokenizer from llama_stack.models.llama.sku_list import resolve_model +from llama_stack.models.llama.sku_types import ModelFamily, is_multimodal from llama_stack.providers.utils.inference import supported_inference_models log = get_logger(name=__name__, category="inference") diff --git a/llama_stack/templates/dependencies.json b/llama_stack/templates/dependencies.json index 931240d37..b8f475cea 100644 --- a/llama_stack/templates/dependencies.json +++ b/llama_stack/templates/dependencies.json @@ -356,50 +356,7 @@ "fairscale", "faiss-cpu", "fastapi", - "fire", - "httpx", - "langdetect", - "lm-format-enforcer", - "matplotlib", - "mcp", - "nltk", - "numpy", - "openai", - "opentelemetry-exporter-otlp-proto-http", - "opentelemetry-sdk", - "pandas", - "pillow", - "psycopg2-binary", - "pymongo", - "pypdf", - "pythainlp", - "redis", - "requests", - "scikit-learn", - "scipy", - "sentence-transformers", - "sentencepiece", - "torch", - "torchvision", - "tqdm", - "transformers", - "tree_sitter", - "uvicorn", - "zmq" - ], - "meta-reference-quantized-gpu": [ - "accelerate", - "aiosqlite", - "autoevals", - "blobfile", - "chardet", - "chromadb-client", - "datasets", - "emoji", - "fairscale", - "faiss-cpu", - "fastapi", - "fbgemm-gpu", + "fbgemm-gpu-genai==1.1.2", "fire", "httpx", "langdetect", diff --git a/llama_stack/templates/meta-reference-gpu/run-with-safety.yaml b/llama_stack/templates/meta-reference-gpu/run-with-safety.yaml index 2cf49cc36..9f97158f8 100644 --- a/llama_stack/templates/meta-reference-gpu/run-with-safety.yaml +++ b/llama_stack/templates/meta-reference-gpu/run-with-safety.yaml @@ -18,6 +18,9 @@ providers: model: ${env.INFERENCE_MODEL} max_seq_len: 4096 checkpoint_dir: ${env.INFERENCE_CHECKPOINT_DIR:null} + quantization: + type: ${env.QUANTIZATION_TYPE:bf16} + model_parallel_size: ${env.MODEL_PARALLEL_SIZE:0} - provider_id: sentence-transformers provider_type: inline::sentence-transformers config: {} @@ -27,6 +30,9 @@ providers: model: ${env.SAFETY_MODEL} max_seq_len: 4096 checkpoint_dir: ${env.SAFETY_CHECKPOINT_DIR:null} + quantization: + type: ${env.QUANTIZATION_TYPE:bf16} + model_parallel_size: ${env.MODEL_PARALLEL_SIZE:0} vector_io: - provider_id: faiss provider_type: inline::faiss diff --git a/llama_stack/templates/meta-reference-gpu/run.yaml b/llama_stack/templates/meta-reference-gpu/run.yaml index 964dfafeb..eda332123 100644 --- a/llama_stack/templates/meta-reference-gpu/run.yaml +++ b/llama_stack/templates/meta-reference-gpu/run.yaml @@ -18,6 +18,9 @@ providers: model: ${env.INFERENCE_MODEL} max_seq_len: 4096 checkpoint_dir: ${env.INFERENCE_CHECKPOINT_DIR:null} + quantization: + type: ${env.QUANTIZATION_TYPE:bf16} + model_parallel_size: ${env.MODEL_PARALLEL_SIZE:0} - provider_id: sentence-transformers provider_type: inline::sentence-transformers config: {} diff --git a/llama_stack/templates/meta-reference-quantized-gpu/build.yaml b/llama_stack/templates/meta-reference-quantized-gpu/build.yaml deleted file mode 100644 index 7bbcfe5f2..000000000 --- a/llama_stack/templates/meta-reference-quantized-gpu/build.yaml +++ /dev/null @@ -1,32 +0,0 @@ -version: '2' -distribution_spec: - description: Use Meta Reference with fp8, int4 quantization for running LLM inference - providers: - inference: - - inline::meta-reference-quantized - vector_io: - - inline::faiss - - remote::chromadb - - remote::pgvector - safety: - - inline::llama-guard - agents: - - inline::meta-reference - telemetry: - - inline::meta-reference - eval: - - inline::meta-reference - datasetio: - - remote::huggingface - - inline::localfs - scoring: - - inline::basic - - inline::llm-as-judge - - inline::braintrust - tool_runtime: - - remote::brave-search - - remote::tavily-search - - inline::code-interpreter - - inline::rag-runtime - - remote::model-context-protocol -image_type: conda diff --git a/llama_stack/templates/meta-reference-quantized-gpu/doc_template.md b/llama_stack/templates/meta-reference-quantized-gpu/doc_template.md deleted file mode 100644 index 1855da6c9..000000000 --- a/llama_stack/templates/meta-reference-quantized-gpu/doc_template.md +++ /dev/null @@ -1,113 +0,0 @@ ---- -orphan: true ---- -# Meta Reference Quantized Distribution - -```{toctree} -:maxdepth: 2 -:hidden: - -self -``` - -The `llamastack/distribution-{{ name }}` distribution consists of the following provider configurations: - -{{ providers_table }} - -The only difference vs. the `meta-reference-gpu` distribution is that it has support for more efficient inference -- with fp8, int4 quantization, etc. - -Note that you need access to nvidia GPUs to run this distribution. This distribution is not compatible with CPU-only machines or machines with AMD GPUs. - -{% if run_config_env_vars %} -### Environment Variables - -The following environment variables can be configured: - -{% for var, (default_value, description) in run_config_env_vars.items() %} -- `{{ var }}`: {{ description }} (default: `{{ default_value }}`) -{% endfor %} -{% endif %} - - -## Prerequisite: Downloading Models - -Please use `llama model list --downloaded` to check that you have llama model checkpoints downloaded in `~/.llama` before proceeding. See [installation guide](https://llama-stack.readthedocs.io/en/latest/references/llama_cli_reference/download_models.html) here to download the models. Run `llama model list` to see the available models to download, and `llama model download` to download the checkpoints. - -``` -$ llama model list --downloaded -┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┓ -┃ Model ┃ Size ┃ Modified Time ┃ -┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━┩ -│ Llama3.2-1B-Instruct:int4-qlora-eo8 │ 1.53 GB │ 2025-02-26 11:22:28 │ -├─────────────────────────────────────────┼──────────┼─────────────────────┤ -│ Llama3.2-1B │ 2.31 GB │ 2025-02-18 21:48:52 │ -├─────────────────────────────────────────┼──────────┼─────────────────────┤ -│ Prompt-Guard-86M │ 0.02 GB │ 2025-02-26 11:29:28 │ -├─────────────────────────────────────────┼──────────┼─────────────────────┤ -│ Llama3.2-3B-Instruct:int4-spinquant-eo8 │ 3.69 GB │ 2025-02-26 11:37:41 │ -├─────────────────────────────────────────┼──────────┼─────────────────────┤ -│ Llama3.2-3B │ 5.99 GB │ 2025-02-18 21:51:26 │ -├─────────────────────────────────────────┼──────────┼─────────────────────┤ -│ Llama3.1-8B │ 14.97 GB │ 2025-02-16 10:36:37 │ -├─────────────────────────────────────────┼──────────┼─────────────────────┤ -│ Llama3.2-1B-Instruct:int4-spinquant-eo8 │ 1.51 GB │ 2025-02-26 11:35:02 │ -├─────────────────────────────────────────┼──────────┼─────────────────────┤ -│ Llama-Guard-3-1B │ 2.80 GB │ 2025-02-26 11:20:46 │ -├─────────────────────────────────────────┼──────────┼─────────────────────┤ -│ Llama-Guard-3-1B:int4 │ 0.43 GB │ 2025-02-26 11:33:33 │ -└─────────────────────────────────────────┴──────────┴─────────────────────┘ -``` - -## Running the Distribution - -You can do this via Conda (build code) or Docker which has a pre-built image. - -### Via Docker - -This method allows you to get started quickly without having to build the distribution code. - -```bash -LLAMA_STACK_PORT=8321 -docker run \ - -it \ - --pull always \ - -p $LLAMA_STACK_PORT:$LLAMA_STACK_PORT \ - -v ~/.llama:/root/.llama \ - llamastack/distribution-{{ name }} \ - --port $LLAMA_STACK_PORT \ - --env INFERENCE_MODEL=meta-llama/Llama-3.2-3B-Instruct -``` - -If you are using Llama Stack Safety / Shield APIs, use: - -```bash -docker run \ - -it \ - --pull always \ - -p $LLAMA_STACK_PORT:$LLAMA_STACK_PORT \ - -v ~/.llama:/root/.llama \ - llamastack/distribution-{{ name }} \ - --port $LLAMA_STACK_PORT \ - --env INFERENCE_MODEL=meta-llama/Llama-3.2-3B-Instruct \ - --env SAFETY_MODEL=meta-llama/Llama-Guard-3-1B -``` - -### Via Conda - -Make sure you have done `uv pip install llama-stack` and have the Llama Stack CLI available. - -```bash -llama stack build --template {{ name }} --image-type conda -llama stack run distributions/{{ name }}/run.yaml \ - --port $LLAMA_STACK_PORT \ - --env INFERENCE_MODEL=meta-llama/Llama-3.2-3B-Instruct -``` - -If you are using Llama Stack Safety / Shield APIs, use: - -```bash -llama stack run distributions/{{ name }}/run-with-safety.yaml \ - --port $LLAMA_STACK_PORT \ - --env INFERENCE_MODEL=meta-llama/Llama-3.2-3B-Instruct \ - --env SAFETY_MODEL=meta-llama/Llama-Guard-3-1B -``` diff --git a/llama_stack/templates/meta-reference-quantized-gpu/meta_reference.py b/llama_stack/templates/meta-reference-quantized-gpu/meta_reference.py deleted file mode 100644 index c46ea8bc6..000000000 --- a/llama_stack/templates/meta-reference-quantized-gpu/meta_reference.py +++ /dev/null @@ -1,115 +0,0 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the terms described in the LICENSE file in -# the root directory of this source tree. - -from pathlib import Path - -from llama_stack.apis.models.models import ModelType -from llama_stack.distribution.datatypes import ModelInput, Provider, ToolGroupInput -from llama_stack.providers.inline.inference.meta_reference import ( - MetaReferenceQuantizedInferenceConfig, -) -from llama_stack.providers.inline.inference.sentence_transformers import ( - SentenceTransformersInferenceConfig, -) -from llama_stack.providers.inline.vector_io.faiss.config import FaissVectorIOConfig -from llama_stack.templates.template import DistributionTemplate, RunConfigSettings - - -def get_distribution_template() -> DistributionTemplate: - providers = { - "inference": ["inline::meta-reference-quantized"], - "vector_io": ["inline::faiss", "remote::chromadb", "remote::pgvector"], - "safety": ["inline::llama-guard"], - "agents": ["inline::meta-reference"], - "telemetry": ["inline::meta-reference"], - "eval": ["inline::meta-reference"], - "datasetio": ["remote::huggingface", "inline::localfs"], - "scoring": ["inline::basic", "inline::llm-as-judge", "inline::braintrust"], - "tool_runtime": [ - "remote::brave-search", - "remote::tavily-search", - "inline::code-interpreter", - "inline::rag-runtime", - "remote::model-context-protocol", - ], - } - default_tool_groups = [ - ToolGroupInput( - toolgroup_id="builtin::websearch", - provider_id="tavily-search", - ), - ToolGroupInput( - toolgroup_id="builtin::rag", - provider_id="rag-runtime", - ), - ToolGroupInput( - toolgroup_id="builtin::code_interpreter", - provider_id="code-interpreter", - ), - ] - name = "meta-reference-quantized-gpu" - inference_provider = Provider( - provider_id="meta-reference-inference", - provider_type="inline::meta-reference-quantized", - config=MetaReferenceQuantizedInferenceConfig.sample_run_config( - model="${env.INFERENCE_MODEL}", - checkpoint_dir="${env.INFERENCE_CHECKPOINT_DIR:null}", - ), - ) - embedding_provider = Provider( - provider_id="sentence-transformers", - provider_type="inline::sentence-transformers", - config=SentenceTransformersInferenceConfig.sample_run_config(), - ) - vector_io_provider = Provider( - provider_id="faiss", - provider_type="inline::faiss", - config=FaissVectorIOConfig.sample_run_config(f"~/.llama/distributions/{name}"), - ) - - inference_model = ModelInput( - model_id="${env.INFERENCE_MODEL}", - provider_id="meta-reference-inference", - ) - embedding_model = ModelInput( - model_id="all-MiniLM-L6-v2", - provider_id="sentence-transformers", - model_type=ModelType.embedding, - metadata={ - "embedding_dimension": 384, - }, - ) - return DistributionTemplate( - name=name, - distro_type="self_hosted", - description="Use Meta Reference with fp8, int4 quantization for running LLM inference", - template_path=Path(__file__).parent / "doc_template.md", - providers=providers, - run_configs={ - "run.yaml": RunConfigSettings( - provider_overrides={ - "inference": [inference_provider, embedding_provider], - "vector_io": [vector_io_provider], - }, - default_models=[inference_model, embedding_model], - default_tool_groups=default_tool_groups, - ), - }, - run_config_env_vars={ - "LLAMA_STACK_PORT": ( - "8321", - "Port for the Llama Stack distribution server", - ), - "INFERENCE_MODEL": ( - "meta-llama/Llama-3.2-3B-Instruct", - "Inference model loaded into the Meta Reference server", - ), - "INFERENCE_CHECKPOINT_DIR": ( - "null", - "Directory containing the Meta Reference model checkpoint", - ), - }, - ) diff --git a/llama_stack/templates/meta-reference-quantized-gpu/run.yaml b/llama_stack/templates/meta-reference-quantized-gpu/run.yaml deleted file mode 100644 index f934ecfbb..000000000 --- a/llama_stack/templates/meta-reference-quantized-gpu/run.yaml +++ /dev/null @@ -1,134 +0,0 @@ -version: '2' -image_name: meta-reference-quantized-gpu -apis: -- agents -- datasetio -- eval -- inference -- safety -- scoring -- telemetry -- tool_runtime -- vector_io -providers: - inference: - - provider_id: meta-reference-inference - provider_type: inline::meta-reference-quantized - config: - model: ${env.INFERENCE_MODEL} - max_seq_len: 4096 - checkpoint_dir: ${env.INFERENCE_CHECKPOINT_DIR:null} - quantization: - type: fp8 - - provider_id: sentence-transformers - provider_type: inline::sentence-transformers - config: {} - vector_io: - - provider_id: faiss - provider_type: inline::faiss - config: - kvstore: - type: sqlite - namespace: null - db_path: ${env.SQLITE_STORE_DIR:~/.llama/distributions/meta-reference-quantized-gpu}/faiss_store.db - safety: - - provider_id: llama-guard - provider_type: inline::llama-guard - config: - excluded_categories: [] - agents: - - provider_id: meta-reference - provider_type: inline::meta-reference - config: - persistence_store: - type: sqlite - namespace: null - db_path: ${env.SQLITE_STORE_DIR:~/.llama/distributions/meta-reference-quantized-gpu}/agents_store.db - telemetry: - - provider_id: meta-reference - provider_type: inline::meta-reference - config: - service_name: "${env.OTEL_SERVICE_NAME:\u200B}" - sinks: ${env.TELEMETRY_SINKS:console,sqlite} - sqlite_db_path: ${env.SQLITE_DB_PATH:~/.llama/distributions/meta-reference-quantized-gpu/trace_store.db} - eval: - - provider_id: meta-reference - provider_type: inline::meta-reference - config: - kvstore: - type: sqlite - namespace: null - db_path: ${env.SQLITE_STORE_DIR:~/.llama/distributions/meta-reference-quantized-gpu}/meta_reference_eval.db - datasetio: - - provider_id: huggingface - provider_type: remote::huggingface - config: - kvstore: - type: sqlite - namespace: null - db_path: ${env.SQLITE_STORE_DIR:~/.llama/distributions/meta-reference-quantized-gpu}/huggingface_datasetio.db - - provider_id: localfs - provider_type: inline::localfs - config: - kvstore: - type: sqlite - namespace: null - db_path: ${env.SQLITE_STORE_DIR:~/.llama/distributions/meta-reference-quantized-gpu}/localfs_datasetio.db - scoring: - - provider_id: basic - provider_type: inline::basic - config: {} - - provider_id: llm-as-judge - provider_type: inline::llm-as-judge - config: {} - - provider_id: braintrust - provider_type: inline::braintrust - config: - openai_api_key: ${env.OPENAI_API_KEY:} - tool_runtime: - - provider_id: brave-search - provider_type: remote::brave-search - config: - api_key: ${env.BRAVE_SEARCH_API_KEY:} - max_results: 3 - - provider_id: tavily-search - provider_type: remote::tavily-search - config: - api_key: ${env.TAVILY_SEARCH_API_KEY:} - max_results: 3 - - provider_id: code-interpreter - provider_type: inline::code-interpreter - config: {} - - provider_id: rag-runtime - provider_type: inline::rag-runtime - config: {} - - provider_id: model-context-protocol - provider_type: remote::model-context-protocol - config: {} -metadata_store: - type: sqlite - db_path: ${env.SQLITE_STORE_DIR:~/.llama/distributions/meta-reference-quantized-gpu}/registry.db -models: -- metadata: {} - model_id: ${env.INFERENCE_MODEL} - provider_id: meta-reference-inference - model_type: llm -- metadata: - embedding_dimension: 384 - model_id: all-MiniLM-L6-v2 - provider_id: sentence-transformers - model_type: embedding -shields: [] -vector_dbs: [] -datasets: [] -scoring_fns: [] -benchmarks: [] -tool_groups: -- toolgroup_id: builtin::websearch - provider_id: tavily-search -- toolgroup_id: builtin::rag - provider_id: rag-runtime -- toolgroup_id: builtin::code_interpreter - provider_id: code-interpreter -server: - port: 8321 diff --git a/pyproject.toml b/pyproject.toml index 8d8ff4338..8ae7ddbb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -224,9 +224,9 @@ exclude = [ "^llama_stack/providers/inline/eval/meta_reference/eval\\.py$", "^llama_stack/providers/inline/inference/meta_reference/config\\.py$", "^llama_stack/providers/inline/inference/meta_reference/inference\\.py$", - "^llama_stack/providers/inline/inference/meta_reference/llama3/generation\\.py$", - "^llama_stack/providers/inline/inference/meta_reference/llama3/multimodal/model\\.py$", - "^llama_stack/providers/inline/inference/meta_reference/llama4/", + "^llama_stack/models/llama/llama3/generation\\.py$", + "^llama_stack/models/llama/llama3/multimodal/model\\.py$", + "^llama_stack/models/llama/llama4/", "^llama_stack/providers/inline/inference/meta_reference/parallel_utils\\.py$", "^llama_stack/providers/inline/inference/meta_reference/quantization/fp8_impls\\.py$", "^llama_stack/providers/inline/inference/meta_reference/quantization/loader\\.py$", diff --git a/scripts/generate_prompt_format.py b/scripts/generate_prompt_format.py index 08c5bea22..5598e35f6 100755 --- a/scripts/generate_prompt_format.py +++ b/scripts/generate_prompt_format.py @@ -5,13 +5,6 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -# Copyright (c) Meta Platforms, Inc. and affiliates. -# All rights reserved. -# -# This source code is licensed under the terms described in the LICENSE file in -# top-level folder for each specific model found within the models/ directory at -# the top-level of this source tree. - # Run this script: # torchrun --nproc_per_node=8 scripts/generate_prompt_format.py meta-llama/Llama-4-17B-Omni-Instruct-BF16-16E ~/.llama/checkpoints/Llama-4-17B-Omni-Instruct-BF16-16E/ llama_stack.models.llama.llama4.prompts llama_stack/models/llama/llama4/prompt_format.md @@ -22,16 +15,9 @@ from pathlib import Path import fire +from llama_stack.models.llama.llama3.generation import Llama3 +from llama_stack.models.llama.llama4.generation import Llama4 from llama_stack.models.llama.sku_list import resolve_model -from llama_stack.providers.inline.inference.meta_reference.config import ( - MetaReferenceInferenceConfig, -) -from llama_stack.providers.inline.inference.meta_reference.llama3.generation import ( - Llama3, -) -from llama_stack.providers.inline.inference.meta_reference.llama4.generation import ( - Llama4, -) THIS_DIR = Path(__file__).parent.resolve() @@ -50,24 +36,12 @@ def run_main( if not llama_model: raise ValueError(f"Model {model_id} not found") - if not llama4: - config = MetaReferenceInferenceConfig( - model=model_id, - max_seq_len=4096, - max_batch_size=1, - checkpoint_dir=checkpoint_dir, - ) - generator = Llama3.build( - config=config, - model_id=model_id, - llama_model=llama_model, - ) - else: - generator = Llama4.build( - ckpt_dir=checkpoint_dir, - max_seq_len=4096, - max_batch_size=1, - ) + cls = Llama4 if llama4 else Llama3 + generator = cls.build( + ckpt_dir=checkpoint_dir, + max_seq_len=4096, + max_batch_size=1, + ) use_cases = module.usecases() text = "" diff --git a/tests/integration/report.py b/tests/integration/report.py index c07338ce6..a50f51d3f 100644 --- a/tests/integration/report.py +++ b/tests/integration/report.py @@ -11,7 +11,6 @@ import pytest from pytest import CollectReport from termcolor import cprint -from llama_stack.models.llama.datatypes import CoreModelId from llama_stack.models.llama.sku_list import ( all_registered_models, llama3_1_instruct_models, @@ -20,6 +19,7 @@ from llama_stack.models.llama.sku_list import ( llama3_instruct_models, safety_models, ) +from llama_stack.models.llama.sku_types import CoreModelId from llama_stack.providers.datatypes import Api from .metadata import API_MAPS From 7b4eb0967e82eab17029f5064f15f753e36b12fc Mon Sep 17 00:00:00 2001 From: ehhuang Date: Mon, 7 Apr 2025 23:06:28 -0700 Subject: [PATCH 07/24] test: verification on provider's OAI endpoints (#1893) # What does this PR do? ## Test Plan export MODEL=accounts/fireworks/models/llama4-scout-instruct-basic; LLAMA_STACK_CONFIG=verification pytest -s -v tests/integration/inference --vision-model $MODEL --text-model $MODEL --- .../self_hosted_distro/fireworks.md | 2 + .../distributions/self_hosted_distro/groq.md | 2 + .../self_hosted_distro/sambanova.md | 1 + .../self_hosted_distro/together.md | 2 + llama_stack/providers/registry/inference.py | 50 ++ .../cerebras_openai_compat/__init__.py | 17 + .../cerebras_openai_compat/cerebras.py | 30 + .../cerebras_openai_compat/config.py | 38 ++ .../remote/inference/fireworks/models.py | 8 + .../fireworks_openai_compat/__init__.py | 17 + .../fireworks_openai_compat/config.py | 38 ++ .../fireworks_openai_compat/fireworks.py | 30 + .../providers/remote/inference/groq/models.py | 8 + .../inference/groq_openai_compat/__init__.py | 17 + .../inference/groq_openai_compat/config.py | 38 ++ .../inference/groq_openai_compat/groq.py | 30 + .../remote/inference/sambanova/models.py | 4 + .../sambanova_openai_compat/__init__.py | 17 + .../sambanova_openai_compat/config.py | 38 ++ .../sambanova_openai_compat/sambanova.py | 30 + .../remote/inference/together/models.py | 14 + .../remote/inference/together/together.py | 2 +- .../together_openai_compat/__init__.py | 17 + .../together_openai_compat/config.py | 38 ++ .../together_openai_compat/together.py | 30 + .../utils/inference/model_registry.py | 13 +- .../utils/inference/openai_compat.py | 32 +- llama_stack/templates/ci-tests/run.yaml | 20 + llama_stack/templates/dependencies.json | 39 ++ llama_stack/templates/dev/run.yaml | 40 ++ .../templates/fireworks/run-with-safety.yaml | 20 + llama_stack/templates/fireworks/run.yaml | 20 + llama_stack/templates/groq/run.yaml | 20 + llama_stack/templates/sambanova/run.yaml | 10 + .../templates/together/run-with-safety.yaml | 30 + llama_stack/templates/together/run.yaml | 30 + .../templates/verification/__init__.py | 7 + llama_stack/templates/verification/build.yaml | 38 ++ llama_stack/templates/verification/run.yaml | 626 ++++++++++++++++++ .../templates/verification/verification.py | 206 ++++++ .../inference/test_text_inference.py | 22 +- .../inference/test_vision_inference.py | 5 +- .../test_cases/inference/chat_completion.json | 4 +- 43 files changed, 1683 insertions(+), 17 deletions(-) create mode 100644 llama_stack/providers/remote/inference/cerebras_openai_compat/__init__.py create mode 100644 llama_stack/providers/remote/inference/cerebras_openai_compat/cerebras.py create mode 100644 llama_stack/providers/remote/inference/cerebras_openai_compat/config.py create mode 100644 llama_stack/providers/remote/inference/fireworks_openai_compat/__init__.py create mode 100644 llama_stack/providers/remote/inference/fireworks_openai_compat/config.py create mode 100644 llama_stack/providers/remote/inference/fireworks_openai_compat/fireworks.py create mode 100644 llama_stack/providers/remote/inference/groq_openai_compat/__init__.py create mode 100644 llama_stack/providers/remote/inference/groq_openai_compat/config.py create mode 100644 llama_stack/providers/remote/inference/groq_openai_compat/groq.py create mode 100644 llama_stack/providers/remote/inference/sambanova_openai_compat/__init__.py create mode 100644 llama_stack/providers/remote/inference/sambanova_openai_compat/config.py create mode 100644 llama_stack/providers/remote/inference/sambanova_openai_compat/sambanova.py create mode 100644 llama_stack/providers/remote/inference/together_openai_compat/__init__.py create mode 100644 llama_stack/providers/remote/inference/together_openai_compat/config.py create mode 100644 llama_stack/providers/remote/inference/together_openai_compat/together.py create mode 100644 llama_stack/templates/verification/__init__.py create mode 100644 llama_stack/templates/verification/build.yaml create mode 100644 llama_stack/templates/verification/run.yaml create mode 100644 llama_stack/templates/verification/verification.py diff --git a/docs/source/distributions/self_hosted_distro/fireworks.md b/docs/source/distributions/self_hosted_distro/fireworks.md index ee4bf0b25..ee9ddc818 100644 --- a/docs/source/distributions/self_hosted_distro/fireworks.md +++ b/docs/source/distributions/self_hosted_distro/fireworks.md @@ -46,6 +46,8 @@ The following models are available by default: - `accounts/fireworks/models/llama-v3p3-70b-instruct (aliases: meta-llama/Llama-3.3-70B-Instruct)` - `accounts/fireworks/models/llama-guard-3-8b (aliases: meta-llama/Llama-Guard-3-8B)` - `accounts/fireworks/models/llama-guard-3-11b-vision (aliases: meta-llama/Llama-Guard-3-11B-Vision)` +- `accounts/fireworks/models/llama4-scout-instruct-basic (aliases: meta-llama/Llama-4-Scout-17B-16E-Instruct)` +- `accounts/fireworks/models/llama4-maverick-instruct-basic (aliases: meta-llama/Llama-4-Maverick-17B-128E-Instruct)` - `nomic-ai/nomic-embed-text-v1.5 ` diff --git a/docs/source/distributions/self_hosted_distro/groq.md b/docs/source/distributions/self_hosted_distro/groq.md index fe922f23d..4f5a8a859 100644 --- a/docs/source/distributions/self_hosted_distro/groq.md +++ b/docs/source/distributions/self_hosted_distro/groq.md @@ -42,6 +42,8 @@ The following models are available by default: - `groq/llama3-70b-8192 (aliases: meta-llama/Llama-3-70B-Instruct)` - `groq/llama-3.3-70b-versatile (aliases: meta-llama/Llama-3.3-70B-Instruct)` - `groq/llama-3.2-3b-preview (aliases: meta-llama/Llama-3.2-3B-Instruct)` +- `groq/llama-4-scout-17b-16e-instruct (aliases: meta-llama/Llama-4-Scout-17B-16E-Instruct)` +- `groq/llama-4-maverick-17b-128e-instruct (aliases: meta-llama/Llama-4-Maverick-17B-128E-Instruct)` ### Prerequisite: API Keys diff --git a/docs/source/distributions/self_hosted_distro/sambanova.md b/docs/source/distributions/self_hosted_distro/sambanova.md index 1d2e0d9df..76b976d78 100644 --- a/docs/source/distributions/self_hosted_distro/sambanova.md +++ b/docs/source/distributions/self_hosted_distro/sambanova.md @@ -43,6 +43,7 @@ The following models are available by default: - `Llama-3.2-11B-Vision-Instruct (aliases: meta-llama/Llama-3.2-11B-Vision-Instruct)` - `Llama-3.2-90B-Vision-Instruct (aliases: meta-llama/Llama-3.2-90B-Vision-Instruct)` - `Meta-Llama-Guard-3-8B (aliases: meta-llama/Llama-Guard-3-8B)` +- `Llama-4-Scout-17B-16E-Instruct (aliases: meta-llama/Llama-4-Scout-17B-16E-Instruct)` ### Prerequisite: API Keys diff --git a/docs/source/distributions/self_hosted_distro/together.md b/docs/source/distributions/self_hosted_distro/together.md index b07e85a1c..3ebb1f59e 100644 --- a/docs/source/distributions/self_hosted_distro/together.md +++ b/docs/source/distributions/self_hosted_distro/together.md @@ -48,6 +48,8 @@ The following models are available by default: - `meta-llama/Llama-Guard-3-11B-Vision-Turbo (aliases: meta-llama/Llama-Guard-3-11B-Vision)` - `togethercomputer/m2-bert-80M-8k-retrieval ` - `togethercomputer/m2-bert-80M-32k-retrieval ` +- `meta-llama/Llama-4-Scout-17B-16E-Instruct (aliases: meta-llama/Llama-4-Scout-17B-16E-Instruct, together/meta-llama/Llama-4-Scout-17B-16E-Instruct)` +- `meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8 (aliases: meta-llama/Llama-4-Maverick-17B-128E-Instruct, together/meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8)` ### Prerequisite: API Keys diff --git a/llama_stack/providers/registry/inference.py b/llama_stack/providers/registry/inference.py index 5f9ae421f..aabb3bbdf 100644 --- a/llama_stack/providers/registry/inference.py +++ b/llama_stack/providers/registry/inference.py @@ -217,6 +217,56 @@ def available_providers() -> List[ProviderSpec]: provider_data_validator="llama_stack.providers.remote.inference.groq.config.GroqProviderDataValidator", ), ), + remote_provider_spec( + api=Api.inference, + adapter=AdapterSpec( + adapter_type="fireworks-openai-compat", + pip_packages=["litellm"], + module="llama_stack.providers.remote.inference.fireworks_openai_compat", + config_class="llama_stack.providers.remote.inference.fireworks_openai_compat.config.FireworksCompatConfig", + provider_data_validator="llama_stack.providers.remote.inference.fireworks_openai_compat.config.FireworksProviderDataValidator", + ), + ), + remote_provider_spec( + api=Api.inference, + adapter=AdapterSpec( + adapter_type="together-openai-compat", + pip_packages=["litellm"], + module="llama_stack.providers.remote.inference.together_openai_compat", + config_class="llama_stack.providers.remote.inference.together_openai_compat.config.TogetherCompatConfig", + provider_data_validator="llama_stack.providers.remote.inference.together_openai_compat.config.TogetherProviderDataValidator", + ), + ), + remote_provider_spec( + api=Api.inference, + adapter=AdapterSpec( + adapter_type="groq-openai-compat", + pip_packages=["litellm"], + module="llama_stack.providers.remote.inference.groq_openai_compat", + config_class="llama_stack.providers.remote.inference.groq_openai_compat.config.GroqCompatConfig", + provider_data_validator="llama_stack.providers.remote.inference.groq_openai_compat.config.GroqProviderDataValidator", + ), + ), + remote_provider_spec( + api=Api.inference, + adapter=AdapterSpec( + adapter_type="sambanova-openai-compat", + pip_packages=["litellm"], + module="llama_stack.providers.remote.inference.sambanova_openai_compat", + config_class="llama_stack.providers.remote.inference.sambanova_openai_compat.config.SambaNovaCompatConfig", + provider_data_validator="llama_stack.providers.remote.inference.sambanova_openai_compat.config.SambaNovaProviderDataValidator", + ), + ), + remote_provider_spec( + api=Api.inference, + adapter=AdapterSpec( + adapter_type="cerebras-openai-compat", + pip_packages=["litellm"], + module="llama_stack.providers.remote.inference.cerebras_openai_compat", + config_class="llama_stack.providers.remote.inference.cerebras_openai_compat.config.CerebrasCompatConfig", + provider_data_validator="llama_stack.providers.remote.inference.cerebras_openai_compat.config.CerebrasProviderDataValidator", + ), + ), remote_provider_spec( api=Api.inference, adapter=AdapterSpec( diff --git a/llama_stack/providers/remote/inference/cerebras_openai_compat/__init__.py b/llama_stack/providers/remote/inference/cerebras_openai_compat/__init__.py new file mode 100644 index 000000000..a5f07edd2 --- /dev/null +++ b/llama_stack/providers/remote/inference/cerebras_openai_compat/__init__.py @@ -0,0 +1,17 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from llama_stack.apis.inference import Inference + +from .config import CerebrasCompatConfig + + +async def get_adapter_impl(config: CerebrasCompatConfig, _deps) -> Inference: + # import dynamically so the import is used only when it is needed + from .cerebras import CerebrasCompatInferenceAdapter + + adapter = CerebrasCompatInferenceAdapter(config) + return adapter diff --git a/llama_stack/providers/remote/inference/cerebras_openai_compat/cerebras.py b/llama_stack/providers/remote/inference/cerebras_openai_compat/cerebras.py new file mode 100644 index 000000000..b3f109dcc --- /dev/null +++ b/llama_stack/providers/remote/inference/cerebras_openai_compat/cerebras.py @@ -0,0 +1,30 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from llama_stack.providers.remote.inference.cerebras_openai_compat.config import CerebrasCompatConfig +from llama_stack.providers.utils.inference.litellm_openai_mixin import LiteLLMOpenAIMixin + +from ..cerebras.models import MODEL_ENTRIES + + +class CerebrasCompatInferenceAdapter(LiteLLMOpenAIMixin): + _config: CerebrasCompatConfig + + def __init__(self, config: CerebrasCompatConfig): + LiteLLMOpenAIMixin.__init__( + self, + model_entries=MODEL_ENTRIES, + api_key_from_config=config.api_key, + provider_data_api_key_field="cerebras_api_key", + openai_compat_api_base=config.openai_compat_api_base, + ) + self.config = config + + async def initialize(self): + await super().initialize() + + async def shutdown(self): + await super().shutdown() diff --git a/llama_stack/providers/remote/inference/cerebras_openai_compat/config.py b/llama_stack/providers/remote/inference/cerebras_openai_compat/config.py new file mode 100644 index 000000000..149c0a202 --- /dev/null +++ b/llama_stack/providers/remote/inference/cerebras_openai_compat/config.py @@ -0,0 +1,38 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from typing import Any, Dict, Optional + +from pydantic import BaseModel, Field + +from llama_stack.schema_utils import json_schema_type + + +class CerebrasProviderDataValidator(BaseModel): + cerebras_api_key: Optional[str] = Field( + default=None, + description="API key for Cerebras models", + ) + + +@json_schema_type +class CerebrasCompatConfig(BaseModel): + api_key: Optional[str] = Field( + default=None, + description="The Cerebras API key", + ) + + openai_compat_api_base: str = Field( + default="https://api.cerebras.ai/v1", + description="The URL for the Cerebras API server", + ) + + @classmethod + def sample_run_config(cls, api_key: str = "${env.CEREBRAS_API_KEY}", **kwargs) -> Dict[str, Any]: + return { + "openai_compat_api_base": "https://api.cerebras.ai/v1", + "api_key": api_key, + } diff --git a/llama_stack/providers/remote/inference/fireworks/models.py b/llama_stack/providers/remote/inference/fireworks/models.py index 4975d061f..027eeab8d 100644 --- a/llama_stack/providers/remote/inference/fireworks/models.py +++ b/llama_stack/providers/remote/inference/fireworks/models.py @@ -48,6 +48,14 @@ MODEL_ENTRIES = [ "accounts/fireworks/models/llama-guard-3-11b-vision", CoreModelId.llama_guard_3_11b_vision.value, ), + build_hf_repo_model_entry( + "accounts/fireworks/models/llama4-scout-instruct-basic", + CoreModelId.llama4_scout_17b_16e_instruct.value, + ), + build_hf_repo_model_entry( + "accounts/fireworks/models/llama4-maverick-instruct-basic", + CoreModelId.llama4_maverick_17b_128e_instruct.value, + ), ProviderModelEntry( provider_model_id="nomic-ai/nomic-embed-text-v1.5", model_type=ModelType.embedding, diff --git a/llama_stack/providers/remote/inference/fireworks_openai_compat/__init__.py b/llama_stack/providers/remote/inference/fireworks_openai_compat/__init__.py new file mode 100644 index 000000000..f78f218b5 --- /dev/null +++ b/llama_stack/providers/remote/inference/fireworks_openai_compat/__init__.py @@ -0,0 +1,17 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from llama_stack.apis.inference import Inference + +from .config import FireworksCompatConfig + + +async def get_adapter_impl(config: FireworksCompatConfig, _deps) -> Inference: + # import dynamically so the import is used only when it is needed + from .fireworks import FireworksCompatInferenceAdapter + + adapter = FireworksCompatInferenceAdapter(config) + return adapter diff --git a/llama_stack/providers/remote/inference/fireworks_openai_compat/config.py b/llama_stack/providers/remote/inference/fireworks_openai_compat/config.py new file mode 100644 index 000000000..0263d348a --- /dev/null +++ b/llama_stack/providers/remote/inference/fireworks_openai_compat/config.py @@ -0,0 +1,38 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from typing import Any, Dict, Optional + +from pydantic import BaseModel, Field + +from llama_stack.schema_utils import json_schema_type + + +class FireworksProviderDataValidator(BaseModel): + fireworks_api_key: Optional[str] = Field( + default=None, + description="API key for Fireworks models", + ) + + +@json_schema_type +class FireworksCompatConfig(BaseModel): + api_key: Optional[str] = Field( + default=None, + description="The Fireworks API key", + ) + + openai_compat_api_base: str = Field( + default="https://api.fireworks.ai/inference/v1", + description="The URL for the Fireworks API server", + ) + + @classmethod + def sample_run_config(cls, api_key: str = "${env.FIREWORKS_API_KEY}", **kwargs) -> Dict[str, Any]: + return { + "openai_compat_api_base": "https://api.fireworks.ai/inference/v1", + "api_key": api_key, + } diff --git a/llama_stack/providers/remote/inference/fireworks_openai_compat/fireworks.py b/llama_stack/providers/remote/inference/fireworks_openai_compat/fireworks.py new file mode 100644 index 000000000..f6045e0eb --- /dev/null +++ b/llama_stack/providers/remote/inference/fireworks_openai_compat/fireworks.py @@ -0,0 +1,30 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from llama_stack.providers.remote.inference.fireworks_openai_compat.config import FireworksCompatConfig +from llama_stack.providers.utils.inference.litellm_openai_mixin import LiteLLMOpenAIMixin + +from ..fireworks.models import MODEL_ENTRIES + + +class FireworksCompatInferenceAdapter(LiteLLMOpenAIMixin): + _config: FireworksCompatConfig + + def __init__(self, config: FireworksCompatConfig): + LiteLLMOpenAIMixin.__init__( + self, + model_entries=MODEL_ENTRIES, + api_key_from_config=config.api_key, + provider_data_api_key_field="fireworks_api_key", + openai_compat_api_base=config.openai_compat_api_base, + ) + self.config = config + + async def initialize(self): + await super().initialize() + + async def shutdown(self): + await super().shutdown() diff --git a/llama_stack/providers/remote/inference/groq/models.py b/llama_stack/providers/remote/inference/groq/models.py index 08b9b4dc4..d0c10ca62 100644 --- a/llama_stack/providers/remote/inference/groq/models.py +++ b/llama_stack/providers/remote/inference/groq/models.py @@ -35,4 +35,12 @@ MODEL_ENTRIES = [ "groq/llama-3.2-3b-preview", CoreModelId.llama3_2_3b_instruct.value, ), + build_hf_repo_model_entry( + "groq/llama-4-scout-17b-16e-instruct", + CoreModelId.llama4_scout_17b_16e_instruct.value, + ), + build_hf_repo_model_entry( + "groq/llama-4-maverick-17b-128e-instruct", + CoreModelId.llama4_maverick_17b_128e_instruct.value, + ), ] diff --git a/llama_stack/providers/remote/inference/groq_openai_compat/__init__.py b/llama_stack/providers/remote/inference/groq_openai_compat/__init__.py new file mode 100644 index 000000000..8161df20d --- /dev/null +++ b/llama_stack/providers/remote/inference/groq_openai_compat/__init__.py @@ -0,0 +1,17 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from llama_stack.apis.inference import Inference + +from .config import GroqCompatConfig + + +async def get_adapter_impl(config: GroqCompatConfig, _deps) -> Inference: + # import dynamically so the import is used only when it is needed + from .groq import GroqCompatInferenceAdapter + + adapter = GroqCompatInferenceAdapter(config) + return adapter diff --git a/llama_stack/providers/remote/inference/groq_openai_compat/config.py b/llama_stack/providers/remote/inference/groq_openai_compat/config.py new file mode 100644 index 000000000..4b90b4576 --- /dev/null +++ b/llama_stack/providers/remote/inference/groq_openai_compat/config.py @@ -0,0 +1,38 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from typing import Any, Dict, Optional + +from pydantic import BaseModel, Field + +from llama_stack.schema_utils import json_schema_type + + +class GroqProviderDataValidator(BaseModel): + groq_api_key: Optional[str] = Field( + default=None, + description="API key for Groq models", + ) + + +@json_schema_type +class GroqCompatConfig(BaseModel): + api_key: Optional[str] = Field( + default=None, + description="The Groq API key", + ) + + openai_compat_api_base: str = Field( + default="https://api.groq.com/openai/v1", + description="The URL for the Groq API server", + ) + + @classmethod + def sample_run_config(cls, api_key: str = "${env.GROQ_API_KEY}", **kwargs) -> Dict[str, Any]: + return { + "openai_compat_api_base": "https://api.groq.com/openai/v1", + "api_key": api_key, + } diff --git a/llama_stack/providers/remote/inference/groq_openai_compat/groq.py b/llama_stack/providers/remote/inference/groq_openai_compat/groq.py new file mode 100644 index 000000000..30e18cd06 --- /dev/null +++ b/llama_stack/providers/remote/inference/groq_openai_compat/groq.py @@ -0,0 +1,30 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from llama_stack.providers.remote.inference.groq_openai_compat.config import GroqCompatConfig +from llama_stack.providers.utils.inference.litellm_openai_mixin import LiteLLMOpenAIMixin + +from ..groq.models import MODEL_ENTRIES + + +class GroqCompatInferenceAdapter(LiteLLMOpenAIMixin): + _config: GroqCompatConfig + + def __init__(self, config: GroqCompatConfig): + LiteLLMOpenAIMixin.__init__( + self, + model_entries=MODEL_ENTRIES, + api_key_from_config=config.api_key, + provider_data_api_key_field="groq_api_key", + openai_compat_api_base=config.openai_compat_api_base, + ) + self.config = config + + async def initialize(self): + await super().initialize() + + async def shutdown(self): + await super().shutdown() diff --git a/llama_stack/providers/remote/inference/sambanova/models.py b/llama_stack/providers/remote/inference/sambanova/models.py index 9589ea268..43041e94a 100644 --- a/llama_stack/providers/remote/inference/sambanova/models.py +++ b/llama_stack/providers/remote/inference/sambanova/models.py @@ -46,4 +46,8 @@ MODEL_ENTRIES = [ "Meta-Llama-Guard-3-8B", CoreModelId.llama_guard_3_8b.value, ), + build_hf_repo_model_entry( + "Llama-4-Scout-17B-16E-Instruct", + CoreModelId.llama4_scout_17b_16e_instruct.value, + ), ] diff --git a/llama_stack/providers/remote/inference/sambanova_openai_compat/__init__.py b/llama_stack/providers/remote/inference/sambanova_openai_compat/__init__.py new file mode 100644 index 000000000..e31a3364c --- /dev/null +++ b/llama_stack/providers/remote/inference/sambanova_openai_compat/__init__.py @@ -0,0 +1,17 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from llama_stack.apis.inference import Inference + +from .config import SambaNovaCompatConfig + + +async def get_adapter_impl(config: SambaNovaCompatConfig, _deps) -> Inference: + # import dynamically so the import is used only when it is needed + from .sambanova import SambaNovaCompatInferenceAdapter + + adapter = SambaNovaCompatInferenceAdapter(config) + return adapter diff --git a/llama_stack/providers/remote/inference/sambanova_openai_compat/config.py b/llama_stack/providers/remote/inference/sambanova_openai_compat/config.py new file mode 100644 index 000000000..b792cb6e7 --- /dev/null +++ b/llama_stack/providers/remote/inference/sambanova_openai_compat/config.py @@ -0,0 +1,38 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from typing import Any, Dict, Optional + +from pydantic import BaseModel, Field + +from llama_stack.schema_utils import json_schema_type + + +class SambaNovaProviderDataValidator(BaseModel): + sambanova_api_key: Optional[str] = Field( + default=None, + description="API key for SambaNova models", + ) + + +@json_schema_type +class SambaNovaCompatConfig(BaseModel): + api_key: Optional[str] = Field( + default=None, + description="The SambaNova API key", + ) + + openai_compat_api_base: str = Field( + default="https://api.sambanova.ai/v1", + description="The URL for the SambaNova API server", + ) + + @classmethod + def sample_run_config(cls, api_key: str = "${env.SAMBANOVA_API_KEY}", **kwargs) -> Dict[str, Any]: + return { + "openai_compat_api_base": "https://api.sambanova.ai/v1", + "api_key": api_key, + } diff --git a/llama_stack/providers/remote/inference/sambanova_openai_compat/sambanova.py b/llama_stack/providers/remote/inference/sambanova_openai_compat/sambanova.py new file mode 100644 index 000000000..aa59028b6 --- /dev/null +++ b/llama_stack/providers/remote/inference/sambanova_openai_compat/sambanova.py @@ -0,0 +1,30 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from llama_stack.providers.remote.inference.sambanova_openai_compat.config import SambaNovaCompatConfig +from llama_stack.providers.utils.inference.litellm_openai_mixin import LiteLLMOpenAIMixin + +from ..sambanova.models import MODEL_ENTRIES + + +class SambaNovaCompatInferenceAdapter(LiteLLMOpenAIMixin): + _config: SambaNovaCompatConfig + + def __init__(self, config: SambaNovaCompatConfig): + LiteLLMOpenAIMixin.__init__( + self, + model_entries=MODEL_ENTRIES, + api_key_from_config=config.api_key, + provider_data_api_key_field="sambanova_api_key", + openai_compat_api_base=config.openai_compat_api_base, + ) + self.config = config + + async def initialize(self): + await super().initialize() + + async def shutdown(self): + await super().shutdown() diff --git a/llama_stack/providers/remote/inference/together/models.py b/llama_stack/providers/remote/inference/together/models.py index f014c03f0..f4b259767 100644 --- a/llama_stack/providers/remote/inference/together/models.py +++ b/llama_stack/providers/remote/inference/together/models.py @@ -64,4 +64,18 @@ MODEL_ENTRIES = [ "context_length": 32768, }, ), + build_hf_repo_model_entry( + "meta-llama/Llama-4-Scout-17B-16E-Instruct", + CoreModelId.llama4_scout_17b_16e_instruct.value, + additional_aliases=[ + "together/meta-llama/Llama-4-Scout-17B-16E-Instruct", + ], + ), + build_hf_repo_model_entry( + "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", + CoreModelId.llama4_maverick_17b_128e_instruct.value, + additional_aliases=[ + "together/meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", + ], + ), ] diff --git a/llama_stack/providers/remote/inference/together/together.py b/llama_stack/providers/remote/inference/together/together.py index a4e02f2cb..df7610935 100644 --- a/llama_stack/providers/remote/inference/together/together.py +++ b/llama_stack/providers/remote/inference/together/together.py @@ -118,7 +118,7 @@ class TogetherInferenceAdapter(ModelRegistryHelper, Inference, NeedsRequestProvi async def _stream_completion(self, request: CompletionRequest) -> AsyncGenerator: params = await self._get_params(request) - client = await self._get_client() + client = self._get_client() stream = await client.completions.create(**params) async for chunk in process_completion_stream_response(stream): yield chunk diff --git a/llama_stack/providers/remote/inference/together_openai_compat/__init__.py b/llama_stack/providers/remote/inference/together_openai_compat/__init__.py new file mode 100644 index 000000000..6fdf05b7e --- /dev/null +++ b/llama_stack/providers/remote/inference/together_openai_compat/__init__.py @@ -0,0 +1,17 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from llama_stack.apis.inference import Inference + +from .config import TogetherCompatConfig + + +async def get_adapter_impl(config: TogetherCompatConfig, _deps) -> Inference: + # import dynamically so the import is used only when it is needed + from .together import TogetherCompatInferenceAdapter + + adapter = TogetherCompatInferenceAdapter(config) + return adapter diff --git a/llama_stack/providers/remote/inference/together_openai_compat/config.py b/llama_stack/providers/remote/inference/together_openai_compat/config.py new file mode 100644 index 000000000..120adbed9 --- /dev/null +++ b/llama_stack/providers/remote/inference/together_openai_compat/config.py @@ -0,0 +1,38 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from typing import Any, Dict, Optional + +from pydantic import BaseModel, Field + +from llama_stack.schema_utils import json_schema_type + + +class TogetherProviderDataValidator(BaseModel): + together_api_key: Optional[str] = Field( + default=None, + description="API key for Together models", + ) + + +@json_schema_type +class TogetherCompatConfig(BaseModel): + api_key: Optional[str] = Field( + default=None, + description="The Together API key", + ) + + openai_compat_api_base: str = Field( + default="https://api.together.xyz/v1", + description="The URL for the Together API server", + ) + + @classmethod + def sample_run_config(cls, api_key: str = "${env.TOGETHER_API_KEY}", **kwargs) -> Dict[str, Any]: + return { + "openai_compat_api_base": "https://api.together.xyz/v1", + "api_key": api_key, + } diff --git a/llama_stack/providers/remote/inference/together_openai_compat/together.py b/llama_stack/providers/remote/inference/together_openai_compat/together.py new file mode 100644 index 000000000..b463f5c35 --- /dev/null +++ b/llama_stack/providers/remote/inference/together_openai_compat/together.py @@ -0,0 +1,30 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from llama_stack.providers.remote.inference.together_openai_compat.config import TogetherCompatConfig +from llama_stack.providers.utils.inference.litellm_openai_mixin import LiteLLMOpenAIMixin + +from ..together.models import MODEL_ENTRIES + + +class TogetherCompatInferenceAdapter(LiteLLMOpenAIMixin): + _config: TogetherCompatConfig + + def __init__(self, config: TogetherCompatConfig): + LiteLLMOpenAIMixin.__init__( + self, + model_entries=MODEL_ENTRIES, + api_key_from_config=config.api_key, + provider_data_api_key_field="together_api_key", + openai_compat_api_base=config.openai_compat_api_base, + ) + self.config = config + + async def initialize(self): + await super().initialize() + + async def shutdown(self): + await super().shutdown() diff --git a/llama_stack/providers/utils/inference/model_registry.py b/llama_stack/providers/utils/inference/model_registry.py index a11c734df..4d7063953 100644 --- a/llama_stack/providers/utils/inference/model_registry.py +++ b/llama_stack/providers/utils/inference/model_registry.py @@ -33,12 +33,17 @@ def get_huggingface_repo(model_descriptor: str) -> Optional[str]: return None -def build_hf_repo_model_entry(provider_model_id: str, model_descriptor: str) -> ProviderModelEntry: +def build_hf_repo_model_entry( + provider_model_id: str, model_descriptor: str, additional_aliases: Optional[List[str]] = None +) -> ProviderModelEntry: + aliases = [ + get_huggingface_repo(model_descriptor), + ] + if additional_aliases: + aliases.extend(additional_aliases) return ProviderModelEntry( provider_model_id=provider_model_id, - aliases=[ - get_huggingface_repo(model_descriptor), - ], + aliases=aliases, llama_model=model_descriptor, ) diff --git a/llama_stack/providers/utils/inference/openai_compat.py b/llama_stack/providers/utils/inference/openai_compat.py index 44a89dfb0..0f3945b34 100644 --- a/llama_stack/providers/utils/inference/openai_compat.py +++ b/llama_stack/providers/utils/inference/openai_compat.py @@ -642,6 +642,36 @@ PYTHON_TYPE_TO_LITELLM_TYPE = { } +def to_openai_param_type(param_type: str) -> dict: + """ + Convert Python type hints to OpenAI parameter type format. + + Examples: + 'str' -> {'type': 'string'} + 'int' -> {'type': 'integer'} + 'list[str]' -> {'type': 'array', 'items': {'type': 'string'}} + 'list[int]' -> {'type': 'array', 'items': {'type': 'integer'}} + """ + # Handle basic types first + basic_types = { + "str": "string", + "int": "integer", + "float": "number", + "bool": "boolean", + } + + if param_type in basic_types: + return {"type": basic_types[param_type]} + + # Handle list/array types + if param_type.startswith("list[") and param_type.endswith("]"): + inner_type = param_type[5:-1] + if inner_type in basic_types: + return {"type": "array", "items": {"type": basic_types.get(inner_type, inner_type)}} + + return {"type": param_type} + + def convert_tooldef_to_openai_tool(tool: ToolDefinition) -> dict: """ Convert a ToolDefinition to an OpenAI API-compatible dictionary. @@ -702,7 +732,7 @@ def convert_tooldef_to_openai_tool(tool: ToolDefinition) -> dict: properties = parameters["properties"] required = [] for param_name, param in tool.parameters.items(): - properties[param_name] = {"type": PYTHON_TYPE_TO_LITELLM_TYPE.get(param.param_type, param.param_type)} + properties[param_name] = to_openai_param_type(param.param_type) if param.description: properties[param_name].update(description=param.description) if param.default: diff --git a/llama_stack/templates/ci-tests/run.yaml b/llama_stack/templates/ci-tests/run.yaml index 04bbe212e..3c16dd5ea 100644 --- a/llama_stack/templates/ci-tests/run.yaml +++ b/llama_stack/templates/ci-tests/run.yaml @@ -193,6 +193,26 @@ models: provider_id: fireworks provider_model_id: accounts/fireworks/models/llama-guard-3-11b-vision model_type: llm +- metadata: {} + model_id: accounts/fireworks/models/llama4-scout-instruct-basic + provider_id: fireworks + provider_model_id: accounts/fireworks/models/llama4-scout-instruct-basic + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + provider_id: fireworks + provider_model_id: accounts/fireworks/models/llama4-scout-instruct-basic + model_type: llm +- metadata: {} + model_id: accounts/fireworks/models/llama4-maverick-instruct-basic + provider_id: fireworks + provider_model_id: accounts/fireworks/models/llama4-maverick-instruct-basic + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct + provider_id: fireworks + provider_model_id: accounts/fireworks/models/llama4-maverick-instruct-basic + model_type: llm - metadata: embedding_dimension: 768 context_length: 8192 diff --git a/llama_stack/templates/dependencies.json b/llama_stack/templates/dependencies.json index b8f475cea..053d6ef8a 100644 --- a/llama_stack/templates/dependencies.json +++ b/llama_stack/templates/dependencies.json @@ -682,6 +682,45 @@ "sentence-transformers --no-deps", "torch torchvision --index-url https://download.pytorch.org/whl/cpu" ], + "verification": [ + "aiosqlite", + "autoevals", + "blobfile", + "chardet", + "chromadb-client", + "datasets", + "emoji", + "fastapi", + "fire", + "httpx", + "langdetect", + "litellm", + "matplotlib", + "mcp", + "nltk", + "numpy", + "openai", + "opentelemetry-exporter-otlp-proto-http", + "opentelemetry-sdk", + "pandas", + "pillow", + "psycopg2-binary", + "pymongo", + "pypdf", + "pythainlp", + "redis", + "requests", + "scikit-learn", + "scipy", + "sentencepiece", + "sqlite-vec", + "tqdm", + "transformers", + "tree_sitter", + "uvicorn", + "sentence-transformers --no-deps", + "torch torchvision --index-url https://download.pytorch.org/whl/cpu" + ], "vllm-gpu": [ "aiosqlite", "autoevals", diff --git a/llama_stack/templates/dev/run.yaml b/llama_stack/templates/dev/run.yaml index b4546ca58..ea3b7252a 100644 --- a/llama_stack/templates/dev/run.yaml +++ b/llama_stack/templates/dev/run.yaml @@ -251,6 +251,26 @@ models: provider_id: fireworks provider_model_id: accounts/fireworks/models/llama-guard-3-11b-vision model_type: llm +- metadata: {} + model_id: accounts/fireworks/models/llama4-scout-instruct-basic + provider_id: fireworks + provider_model_id: accounts/fireworks/models/llama4-scout-instruct-basic + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + provider_id: fireworks + provider_model_id: accounts/fireworks/models/llama4-scout-instruct-basic + model_type: llm +- metadata: {} + model_id: accounts/fireworks/models/llama4-maverick-instruct-basic + provider_id: fireworks + provider_model_id: accounts/fireworks/models/llama4-maverick-instruct-basic + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct + provider_id: fireworks + provider_model_id: accounts/fireworks/models/llama4-maverick-instruct-basic + model_type: llm - metadata: embedding_dimension: 768 context_length: 8192 @@ -356,6 +376,26 @@ models: provider_id: groq provider_model_id: groq/llama-3.2-3b-preview model_type: llm +- metadata: {} + model_id: groq/llama-4-scout-17b-16e-instruct + provider_id: groq + provider_model_id: groq/llama-4-scout-17b-16e-instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + provider_id: groq + provider_model_id: groq/llama-4-scout-17b-16e-instruct + model_type: llm +- metadata: {} + model_id: groq/llama-4-maverick-17b-128e-instruct + provider_id: groq + provider_model_id: groq/llama-4-maverick-17b-128e-instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct + provider_id: groq + provider_model_id: groq/llama-4-maverick-17b-128e-instruct + model_type: llm - metadata: embedding_dimension: 384 model_id: all-MiniLM-L6-v2 diff --git a/llama_stack/templates/fireworks/run-with-safety.yaml b/llama_stack/templates/fireworks/run-with-safety.yaml index 125c66177..aa6209db6 100644 --- a/llama_stack/templates/fireworks/run-with-safety.yaml +++ b/llama_stack/templates/fireworks/run-with-safety.yaml @@ -205,6 +205,26 @@ models: provider_id: fireworks provider_model_id: accounts/fireworks/models/llama-guard-3-11b-vision model_type: llm +- metadata: {} + model_id: accounts/fireworks/models/llama4-scout-instruct-basic + provider_id: fireworks + provider_model_id: accounts/fireworks/models/llama4-scout-instruct-basic + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + provider_id: fireworks + provider_model_id: accounts/fireworks/models/llama4-scout-instruct-basic + model_type: llm +- metadata: {} + model_id: accounts/fireworks/models/llama4-maverick-instruct-basic + provider_id: fireworks + provider_model_id: accounts/fireworks/models/llama4-maverick-instruct-basic + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct + provider_id: fireworks + provider_model_id: accounts/fireworks/models/llama4-maverick-instruct-basic + model_type: llm - metadata: embedding_dimension: 768 context_length: 8192 diff --git a/llama_stack/templates/fireworks/run.yaml b/llama_stack/templates/fireworks/run.yaml index 7b3c059e5..834ec8260 100644 --- a/llama_stack/templates/fireworks/run.yaml +++ b/llama_stack/templates/fireworks/run.yaml @@ -200,6 +200,26 @@ models: provider_id: fireworks provider_model_id: accounts/fireworks/models/llama-guard-3-11b-vision model_type: llm +- metadata: {} + model_id: accounts/fireworks/models/llama4-scout-instruct-basic + provider_id: fireworks + provider_model_id: accounts/fireworks/models/llama4-scout-instruct-basic + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + provider_id: fireworks + provider_model_id: accounts/fireworks/models/llama4-scout-instruct-basic + model_type: llm +- metadata: {} + model_id: accounts/fireworks/models/llama4-maverick-instruct-basic + provider_id: fireworks + provider_model_id: accounts/fireworks/models/llama4-maverick-instruct-basic + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct + provider_id: fireworks + provider_model_id: accounts/fireworks/models/llama4-maverick-instruct-basic + model_type: llm - metadata: embedding_dimension: 768 context_length: 8192 diff --git a/llama_stack/templates/groq/run.yaml b/llama_stack/templates/groq/run.yaml index 6c83ed43d..f557e64fd 100644 --- a/llama_stack/templates/groq/run.yaml +++ b/llama_stack/templates/groq/run.yaml @@ -148,6 +148,26 @@ models: provider_id: groq provider_model_id: groq/llama-3.2-3b-preview model_type: llm +- metadata: {} + model_id: groq/llama-4-scout-17b-16e-instruct + provider_id: groq + provider_model_id: groq/llama-4-scout-17b-16e-instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + provider_id: groq + provider_model_id: groq/llama-4-scout-17b-16e-instruct + model_type: llm +- metadata: {} + model_id: groq/llama-4-maverick-17b-128e-instruct + provider_id: groq + provider_model_id: groq/llama-4-maverick-17b-128e-instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct + provider_id: groq + provider_model_id: groq/llama-4-maverick-17b-128e-instruct + model_type: llm - metadata: embedding_dimension: 384 model_id: all-MiniLM-L6-v2 diff --git a/llama_stack/templates/sambanova/run.yaml b/llama_stack/templates/sambanova/run.yaml index a64ada759..e4e8e4e21 100644 --- a/llama_stack/templates/sambanova/run.yaml +++ b/llama_stack/templates/sambanova/run.yaml @@ -165,6 +165,16 @@ models: provider_id: sambanova provider_model_id: Meta-Llama-Guard-3-8B model_type: llm +- metadata: {} + model_id: Llama-4-Scout-17B-16E-Instruct + provider_id: sambanova + provider_model_id: Llama-4-Scout-17B-16E-Instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + provider_id: sambanova + provider_model_id: Llama-4-Scout-17B-16E-Instruct + model_type: llm shields: - shield_id: meta-llama/Llama-Guard-3-8B vector_dbs: [] diff --git a/llama_stack/templates/together/run-with-safety.yaml b/llama_stack/templates/together/run-with-safety.yaml index 1fbf64e40..105ce896d 100644 --- a/llama_stack/templates/together/run-with-safety.yaml +++ b/llama_stack/templates/together/run-with-safety.yaml @@ -219,6 +219,36 @@ models: provider_id: together provider_model_id: togethercomputer/m2-bert-80M-32k-retrieval model_type: embedding +- metadata: {} + model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + provider_id: together + provider_model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + provider_id: together + provider_model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + model_type: llm +- metadata: {} + model_id: together/meta-llama/Llama-4-Scout-17B-16E-Instruct + provider_id: together + provider_model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8 + provider_id: together + provider_model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8 + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct + provider_id: together + provider_model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8 + model_type: llm +- metadata: {} + model_id: together/meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8 + provider_id: together + provider_model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8 + model_type: llm - metadata: embedding_dimension: 384 model_id: all-MiniLM-L6-v2 diff --git a/llama_stack/templates/together/run.yaml b/llama_stack/templates/together/run.yaml index d71aea640..1f1613655 100644 --- a/llama_stack/templates/together/run.yaml +++ b/llama_stack/templates/together/run.yaml @@ -214,6 +214,36 @@ models: provider_id: together provider_model_id: togethercomputer/m2-bert-80M-32k-retrieval model_type: embedding +- metadata: {} + model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + provider_id: together + provider_model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + provider_id: together + provider_model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + model_type: llm +- metadata: {} + model_id: together/meta-llama/Llama-4-Scout-17B-16E-Instruct + provider_id: together + provider_model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8 + provider_id: together + provider_model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8 + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct + provider_id: together + provider_model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8 + model_type: llm +- metadata: {} + model_id: together/meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8 + provider_id: together + provider_model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8 + model_type: llm - metadata: embedding_dimension: 384 model_id: all-MiniLM-L6-v2 diff --git a/llama_stack/templates/verification/__init__.py b/llama_stack/templates/verification/__init__.py new file mode 100644 index 000000000..5d8c281a6 --- /dev/null +++ b/llama_stack/templates/verification/__init__.py @@ -0,0 +1,7 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from .verification import get_distribution_template # noqa: F401 diff --git a/llama_stack/templates/verification/build.yaml b/llama_stack/templates/verification/build.yaml new file mode 100644 index 000000000..9f010d651 --- /dev/null +++ b/llama_stack/templates/verification/build.yaml @@ -0,0 +1,38 @@ +version: '2' +distribution_spec: + description: Distribution for running e2e tests in CI + providers: + inference: + - remote::openai + - remote::fireworks-openai-compat + - remote::together-openai-compat + - remote::groq-openai-compat + - remote::sambanova-openai-compat + - remote::cerebras-openai-compat + - inline::sentence-transformers + vector_io: + - inline::sqlite-vec + - remote::chromadb + - remote::pgvector + safety: + - inline::llama-guard + agents: + - inline::meta-reference + telemetry: + - inline::meta-reference + eval: + - inline::meta-reference + datasetio: + - remote::huggingface + - inline::localfs + scoring: + - inline::basic + - inline::llm-as-judge + - inline::braintrust + tool_runtime: + - remote::brave-search + - remote::tavily-search + - inline::code-interpreter + - inline::rag-runtime + - remote::model-context-protocol +image_type: conda diff --git a/llama_stack/templates/verification/run.yaml b/llama_stack/templates/verification/run.yaml new file mode 100644 index 000000000..b6c2ca98d --- /dev/null +++ b/llama_stack/templates/verification/run.yaml @@ -0,0 +1,626 @@ +version: '2' +image_name: verification +apis: +- agents +- datasetio +- eval +- inference +- safety +- scoring +- telemetry +- tool_runtime +- vector_io +providers: + inference: + - provider_id: openai + provider_type: remote::openai + config: + api_key: ${env.OPENAI_API_KEY:} + - provider_id: fireworks-openai-compat + provider_type: remote::fireworks-openai-compat + config: + openai_compat_api_base: https://api.fireworks.ai/inference/v1 + api_key: ${env.FIREWORKS_API_KEY:} + - provider_id: together-openai-compat + provider_type: remote::together-openai-compat + config: + openai_compat_api_base: https://api.together.xyz/v1 + api_key: ${env.TOGETHER_API_KEY:} + - provider_id: groq-openai-compat + provider_type: remote::groq-openai-compat + config: + openai_compat_api_base: https://api.groq.com/openai/v1 + api_key: ${env.GROQ_API_KEY:} + - provider_id: sambanova-openai-compat + provider_type: remote::sambanova-openai-compat + config: + openai_compat_api_base: https://api.sambanova.ai/v1 + api_key: ${env.SAMBANOVA_API_KEY:} + - provider_id: cerebras-openai-compat + provider_type: remote::cerebras-openai-compat + config: + openai_compat_api_base: https://api.cerebras.ai/v1 + api_key: ${env.CEREBRAS_API_KEY:} + - provider_id: sentence-transformers + provider_type: inline::sentence-transformers + config: {} + vector_io: + - provider_id: sqlite-vec + provider_type: inline::sqlite-vec + config: + db_path: ${env.SQLITE_STORE_DIR:~/.llama/distributions/verification}/sqlite_vec.db + - provider_id: ${env.ENABLE_CHROMADB+chromadb} + provider_type: remote::chromadb + config: + url: ${env.CHROMADB_URL:} + - provider_id: ${env.ENABLE_PGVECTOR+pgvector} + provider_type: remote::pgvector + config: + host: ${env.PGVECTOR_HOST:localhost} + port: ${env.PGVECTOR_PORT:5432} + db: ${env.PGVECTOR_DB:} + user: ${env.PGVECTOR_USER:} + password: ${env.PGVECTOR_PASSWORD:} + safety: + - provider_id: llama-guard + provider_type: inline::llama-guard + config: + excluded_categories: [] + agents: + - provider_id: meta-reference + provider_type: inline::meta-reference + config: + persistence_store: + type: sqlite + namespace: null + db_path: ${env.SQLITE_STORE_DIR:~/.llama/distributions/verification}/agents_store.db + telemetry: + - provider_id: meta-reference + provider_type: inline::meta-reference + config: + service_name: "${env.OTEL_SERVICE_NAME:\u200B}" + sinks: ${env.TELEMETRY_SINKS:console,sqlite} + sqlite_db_path: ${env.SQLITE_DB_PATH:~/.llama/distributions/verification/trace_store.db} + eval: + - provider_id: meta-reference + provider_type: inline::meta-reference + config: + kvstore: + type: sqlite + namespace: null + db_path: ${env.SQLITE_STORE_DIR:~/.llama/distributions/verification}/meta_reference_eval.db + datasetio: + - provider_id: huggingface + provider_type: remote::huggingface + config: + kvstore: + type: sqlite + namespace: null + db_path: ${env.SQLITE_STORE_DIR:~/.llama/distributions/verification}/huggingface_datasetio.db + - provider_id: localfs + provider_type: inline::localfs + config: + kvstore: + type: sqlite + namespace: null + db_path: ${env.SQLITE_STORE_DIR:~/.llama/distributions/verification}/localfs_datasetio.db + scoring: + - provider_id: basic + provider_type: inline::basic + config: {} + - provider_id: llm-as-judge + provider_type: inline::llm-as-judge + config: {} + - provider_id: braintrust + provider_type: inline::braintrust + config: + openai_api_key: ${env.OPENAI_API_KEY:} + tool_runtime: + - provider_id: brave-search + provider_type: remote::brave-search + config: + api_key: ${env.BRAVE_SEARCH_API_KEY:} + max_results: 3 + - provider_id: tavily-search + provider_type: remote::tavily-search + config: + api_key: ${env.TAVILY_SEARCH_API_KEY:} + max_results: 3 + - provider_id: code-interpreter + provider_type: inline::code-interpreter + config: {} + - provider_id: rag-runtime + provider_type: inline::rag-runtime + config: {} + - provider_id: model-context-protocol + provider_type: remote::model-context-protocol + config: {} +metadata_store: + type: sqlite + db_path: ${env.SQLITE_STORE_DIR:~/.llama/distributions/verification}/registry.db +models: +- metadata: {} + model_id: openai/gpt-4o + provider_id: openai + provider_model_id: openai/gpt-4o + model_type: llm +- metadata: {} + model_id: openai/gpt-4o-mini + provider_id: openai + provider_model_id: openai/gpt-4o-mini + model_type: llm +- metadata: {} + model_id: openai/chatgpt-4o-latest + provider_id: openai + provider_model_id: openai/chatgpt-4o-latest + model_type: llm +- metadata: + embedding_dimension: 1536 + context_length: 8192 + model_id: openai/text-embedding-3-small + provider_id: openai + provider_model_id: openai/text-embedding-3-small + model_type: embedding +- metadata: + embedding_dimension: 3072 + context_length: 8192 + model_id: openai/text-embedding-3-large + provider_id: openai + provider_model_id: openai/text-embedding-3-large + model_type: embedding +- metadata: {} + model_id: accounts/fireworks/models/llama-v3p1-8b-instruct + provider_id: fireworks-openai-compat + provider_model_id: accounts/fireworks/models/llama-v3p1-8b-instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.1-8B-Instruct + provider_id: fireworks-openai-compat + provider_model_id: accounts/fireworks/models/llama-v3p1-8b-instruct + model_type: llm +- metadata: {} + model_id: accounts/fireworks/models/llama-v3p1-70b-instruct + provider_id: fireworks-openai-compat + provider_model_id: accounts/fireworks/models/llama-v3p1-70b-instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.1-70B-Instruct + provider_id: fireworks-openai-compat + provider_model_id: accounts/fireworks/models/llama-v3p1-70b-instruct + model_type: llm +- metadata: {} + model_id: accounts/fireworks/models/llama-v3p1-405b-instruct + provider_id: fireworks-openai-compat + provider_model_id: accounts/fireworks/models/llama-v3p1-405b-instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.1-405B-Instruct-FP8 + provider_id: fireworks-openai-compat + provider_model_id: accounts/fireworks/models/llama-v3p1-405b-instruct + model_type: llm +- metadata: {} + model_id: accounts/fireworks/models/llama-v3p2-3b-instruct + provider_id: fireworks-openai-compat + provider_model_id: accounts/fireworks/models/llama-v3p2-3b-instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.2-3B-Instruct + provider_id: fireworks-openai-compat + provider_model_id: accounts/fireworks/models/llama-v3p2-3b-instruct + model_type: llm +- metadata: {} + model_id: accounts/fireworks/models/llama-v3p2-11b-vision-instruct + provider_id: fireworks-openai-compat + provider_model_id: accounts/fireworks/models/llama-v3p2-11b-vision-instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.2-11B-Vision-Instruct + provider_id: fireworks-openai-compat + provider_model_id: accounts/fireworks/models/llama-v3p2-11b-vision-instruct + model_type: llm +- metadata: {} + model_id: accounts/fireworks/models/llama-v3p2-90b-vision-instruct + provider_id: fireworks-openai-compat + provider_model_id: accounts/fireworks/models/llama-v3p2-90b-vision-instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.2-90B-Vision-Instruct + provider_id: fireworks-openai-compat + provider_model_id: accounts/fireworks/models/llama-v3p2-90b-vision-instruct + model_type: llm +- metadata: {} + model_id: accounts/fireworks/models/llama-v3p3-70b-instruct + provider_id: fireworks-openai-compat + provider_model_id: accounts/fireworks/models/llama-v3p3-70b-instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.3-70B-Instruct + provider_id: fireworks-openai-compat + provider_model_id: accounts/fireworks/models/llama-v3p3-70b-instruct + model_type: llm +- metadata: {} + model_id: accounts/fireworks/models/llama-guard-3-8b + provider_id: fireworks-openai-compat + provider_model_id: accounts/fireworks/models/llama-guard-3-8b + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-Guard-3-8B + provider_id: fireworks-openai-compat + provider_model_id: accounts/fireworks/models/llama-guard-3-8b + model_type: llm +- metadata: {} + model_id: accounts/fireworks/models/llama-guard-3-11b-vision + provider_id: fireworks-openai-compat + provider_model_id: accounts/fireworks/models/llama-guard-3-11b-vision + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-Guard-3-11B-Vision + provider_id: fireworks-openai-compat + provider_model_id: accounts/fireworks/models/llama-guard-3-11b-vision + model_type: llm +- metadata: {} + model_id: accounts/fireworks/models/llama4-scout-instruct-basic + provider_id: fireworks-openai-compat + provider_model_id: accounts/fireworks/models/llama4-scout-instruct-basic + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + provider_id: fireworks-openai-compat + provider_model_id: accounts/fireworks/models/llama4-scout-instruct-basic + model_type: llm +- metadata: {} + model_id: accounts/fireworks/models/llama4-maverick-instruct-basic + provider_id: fireworks-openai-compat + provider_model_id: accounts/fireworks/models/llama4-maverick-instruct-basic + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct + provider_id: fireworks-openai-compat + provider_model_id: accounts/fireworks/models/llama4-maverick-instruct-basic + model_type: llm +- metadata: + embedding_dimension: 768 + context_length: 8192 + model_id: nomic-ai/nomic-embed-text-v1.5 + provider_id: fireworks-openai-compat + provider_model_id: nomic-ai/nomic-embed-text-v1.5 + model_type: embedding +- metadata: {} + model_id: meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo + provider_id: together-openai-compat + provider_model_id: meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.1-8B-Instruct + provider_id: together-openai-compat + provider_model_id: meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo + model_type: llm +- metadata: {} + model_id: meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo + provider_id: together-openai-compat + provider_model_id: meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.1-70B-Instruct + provider_id: together-openai-compat + provider_model_id: meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo + model_type: llm +- metadata: {} + model_id: meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo + provider_id: together-openai-compat + provider_model_id: meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.1-405B-Instruct-FP8 + provider_id: together-openai-compat + provider_model_id: meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.2-3B-Instruct-Turbo + provider_id: together-openai-compat + provider_model_id: meta-llama/Llama-3.2-3B-Instruct-Turbo + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.2-3B-Instruct + provider_id: together-openai-compat + provider_model_id: meta-llama/Llama-3.2-3B-Instruct-Turbo + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo + provider_id: together-openai-compat + provider_model_id: meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.2-11B-Vision-Instruct + provider_id: together-openai-compat + provider_model_id: meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.2-90B-Vision-Instruct-Turbo + provider_id: together-openai-compat + provider_model_id: meta-llama/Llama-3.2-90B-Vision-Instruct-Turbo + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.2-90B-Vision-Instruct + provider_id: together-openai-compat + provider_model_id: meta-llama/Llama-3.2-90B-Vision-Instruct-Turbo + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.3-70B-Instruct-Turbo + provider_id: together-openai-compat + provider_model_id: meta-llama/Llama-3.3-70B-Instruct-Turbo + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.3-70B-Instruct + provider_id: together-openai-compat + provider_model_id: meta-llama/Llama-3.3-70B-Instruct-Turbo + model_type: llm +- metadata: {} + model_id: meta-llama/Meta-Llama-Guard-3-8B + provider_id: together-openai-compat + provider_model_id: meta-llama/Meta-Llama-Guard-3-8B + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-Guard-3-8B + provider_id: together-openai-compat + provider_model_id: meta-llama/Meta-Llama-Guard-3-8B + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-Guard-3-11B-Vision-Turbo + provider_id: together-openai-compat + provider_model_id: meta-llama/Llama-Guard-3-11B-Vision-Turbo + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-Guard-3-11B-Vision + provider_id: together-openai-compat + provider_model_id: meta-llama/Llama-Guard-3-11B-Vision-Turbo + model_type: llm +- metadata: + embedding_dimension: 768 + context_length: 8192 + model_id: togethercomputer/m2-bert-80M-8k-retrieval + provider_id: together-openai-compat + provider_model_id: togethercomputer/m2-bert-80M-8k-retrieval + model_type: embedding +- metadata: + embedding_dimension: 768 + context_length: 32768 + model_id: togethercomputer/m2-bert-80M-32k-retrieval + provider_id: together-openai-compat + provider_model_id: togethercomputer/m2-bert-80M-32k-retrieval + model_type: embedding +- metadata: {} + model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + provider_id: together-openai-compat + provider_model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + provider_id: together-openai-compat + provider_model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + model_type: llm +- metadata: {} + model_id: together/meta-llama/Llama-4-Scout-17B-16E-Instruct + provider_id: together-openai-compat + provider_model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8 + provider_id: together-openai-compat + provider_model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8 + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct + provider_id: together-openai-compat + provider_model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8 + model_type: llm +- metadata: {} + model_id: together/meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8 + provider_id: together-openai-compat + provider_model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8 + model_type: llm +- metadata: {} + model_id: groq/llama3-8b-8192 + provider_id: groq-openai-compat + provider_model_id: groq/llama3-8b-8192 + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.1-8B-Instruct + provider_id: groq-openai-compat + provider_model_id: groq/llama3-8b-8192 + model_type: llm +- metadata: {} + model_id: groq/llama-3.1-8b-instant + provider_id: groq-openai-compat + provider_model_id: groq/llama-3.1-8b-instant + model_type: llm +- metadata: {} + model_id: groq/llama3-70b-8192 + provider_id: groq-openai-compat + provider_model_id: groq/llama3-70b-8192 + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3-70B-Instruct + provider_id: groq-openai-compat + provider_model_id: groq/llama3-70b-8192 + model_type: llm +- metadata: {} + model_id: groq/llama-3.3-70b-versatile + provider_id: groq-openai-compat + provider_model_id: groq/llama-3.3-70b-versatile + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.3-70B-Instruct + provider_id: groq-openai-compat + provider_model_id: groq/llama-3.3-70b-versatile + model_type: llm +- metadata: {} + model_id: groq/llama-3.2-3b-preview + provider_id: groq-openai-compat + provider_model_id: groq/llama-3.2-3b-preview + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.2-3B-Instruct + provider_id: groq-openai-compat + provider_model_id: groq/llama-3.2-3b-preview + model_type: llm +- metadata: {} + model_id: groq/llama-4-scout-17b-16e-instruct + provider_id: groq-openai-compat + provider_model_id: groq/llama-4-scout-17b-16e-instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + provider_id: groq-openai-compat + provider_model_id: groq/llama-4-scout-17b-16e-instruct + model_type: llm +- metadata: {} + model_id: groq/llama-4-maverick-17b-128e-instruct + provider_id: groq-openai-compat + provider_model_id: groq/llama-4-maverick-17b-128e-instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Maverick-17B-128E-Instruct + provider_id: groq-openai-compat + provider_model_id: groq/llama-4-maverick-17b-128e-instruct + model_type: llm +- metadata: {} + model_id: Meta-Llama-3.1-8B-Instruct + provider_id: sambanova-openai-compat + provider_model_id: Meta-Llama-3.1-8B-Instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.1-8B-Instruct + provider_id: sambanova-openai-compat + provider_model_id: Meta-Llama-3.1-8B-Instruct + model_type: llm +- metadata: {} + model_id: Meta-Llama-3.1-70B-Instruct + provider_id: sambanova-openai-compat + provider_model_id: Meta-Llama-3.1-70B-Instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.1-70B-Instruct + provider_id: sambanova-openai-compat + provider_model_id: Meta-Llama-3.1-70B-Instruct + model_type: llm +- metadata: {} + model_id: Meta-Llama-3.1-405B-Instruct + provider_id: sambanova-openai-compat + provider_model_id: Meta-Llama-3.1-405B-Instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.1-405B-Instruct-FP8 + provider_id: sambanova-openai-compat + provider_model_id: Meta-Llama-3.1-405B-Instruct + model_type: llm +- metadata: {} + model_id: Meta-Llama-3.2-1B-Instruct + provider_id: sambanova-openai-compat + provider_model_id: Meta-Llama-3.2-1B-Instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.2-1B-Instruct + provider_id: sambanova-openai-compat + provider_model_id: Meta-Llama-3.2-1B-Instruct + model_type: llm +- metadata: {} + model_id: Meta-Llama-3.2-3B-Instruct + provider_id: sambanova-openai-compat + provider_model_id: Meta-Llama-3.2-3B-Instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.2-3B-Instruct + provider_id: sambanova-openai-compat + provider_model_id: Meta-Llama-3.2-3B-Instruct + model_type: llm +- metadata: {} + model_id: Meta-Llama-3.3-70B-Instruct + provider_id: sambanova-openai-compat + provider_model_id: Meta-Llama-3.3-70B-Instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.3-70B-Instruct + provider_id: sambanova-openai-compat + provider_model_id: Meta-Llama-3.3-70B-Instruct + model_type: llm +- metadata: {} + model_id: Llama-3.2-11B-Vision-Instruct + provider_id: sambanova-openai-compat + provider_model_id: Llama-3.2-11B-Vision-Instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.2-11B-Vision-Instruct + provider_id: sambanova-openai-compat + provider_model_id: Llama-3.2-11B-Vision-Instruct + model_type: llm +- metadata: {} + model_id: Llama-3.2-90B-Vision-Instruct + provider_id: sambanova-openai-compat + provider_model_id: Llama-3.2-90B-Vision-Instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.2-90B-Vision-Instruct + provider_id: sambanova-openai-compat + provider_model_id: Llama-3.2-90B-Vision-Instruct + model_type: llm +- metadata: {} + model_id: Meta-Llama-Guard-3-8B + provider_id: sambanova-openai-compat + provider_model_id: Meta-Llama-Guard-3-8B + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-Guard-3-8B + provider_id: sambanova-openai-compat + provider_model_id: Meta-Llama-Guard-3-8B + model_type: llm +- metadata: {} + model_id: Llama-4-Scout-17B-16E-Instruct + provider_id: sambanova-openai-compat + provider_model_id: Llama-4-Scout-17B-16E-Instruct + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-4-Scout-17B-16E-Instruct + provider_id: sambanova-openai-compat + provider_model_id: Llama-4-Scout-17B-16E-Instruct + model_type: llm +- metadata: {} + model_id: llama3.1-8b + provider_id: cerebras-openai-compat + provider_model_id: llama3.1-8b + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.1-8B-Instruct + provider_id: cerebras-openai-compat + provider_model_id: llama3.1-8b + model_type: llm +- metadata: {} + model_id: llama-3.3-70b + provider_id: cerebras-openai-compat + provider_model_id: llama-3.3-70b + model_type: llm +- metadata: {} + model_id: meta-llama/Llama-3.3-70B-Instruct + provider_id: cerebras-openai-compat + provider_model_id: llama-3.3-70b + model_type: llm +- metadata: + embedding_dimension: 384 + model_id: all-MiniLM-L6-v2 + provider_id: sentence-transformers + model_type: embedding +shields: +- shield_id: meta-llama/Llama-Guard-3-8B +vector_dbs: [] +datasets: [] +scoring_fns: [] +benchmarks: [] +tool_groups: +- toolgroup_id: builtin::websearch + provider_id: tavily-search +- toolgroup_id: builtin::rag + provider_id: rag-runtime +- toolgroup_id: builtin::code_interpreter + provider_id: code-interpreter +server: + port: 8321 diff --git a/llama_stack/templates/verification/verification.py b/llama_stack/templates/verification/verification.py new file mode 100644 index 000000000..7ef8d6a96 --- /dev/null +++ b/llama_stack/templates/verification/verification.py @@ -0,0 +1,206 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from typing import List, Tuple + +from llama_stack.apis.models.models import ModelType +from llama_stack.distribution.datatypes import ( + ModelInput, + Provider, + ShieldInput, + ToolGroupInput, +) +from llama_stack.providers.inline.inference.sentence_transformers import ( + SentenceTransformersInferenceConfig, +) +from llama_stack.providers.inline.vector_io.sqlite_vec.config import ( + SQLiteVectorIOConfig, +) +from llama_stack.providers.remote.inference.cerebras.models import MODEL_ENTRIES as CEREBRAS_MODEL_ENTRIES +from llama_stack.providers.remote.inference.cerebras_openai_compat.config import CerebrasCompatConfig +from llama_stack.providers.remote.inference.fireworks.models import ( + MODEL_ENTRIES as FIREWORKS_MODEL_ENTRIES, +) +from llama_stack.providers.remote.inference.fireworks_openai_compat.config import FireworksCompatConfig +from llama_stack.providers.remote.inference.groq.models import ( + MODEL_ENTRIES as GROQ_MODEL_ENTRIES, +) +from llama_stack.providers.remote.inference.groq_openai_compat.config import GroqCompatConfig +from llama_stack.providers.remote.inference.openai.config import OpenAIConfig +from llama_stack.providers.remote.inference.openai.models import ( + MODEL_ENTRIES as OPENAI_MODEL_ENTRIES, +) +from llama_stack.providers.remote.inference.sambanova.models import MODEL_ENTRIES as SAMBANOVA_MODEL_ENTRIES +from llama_stack.providers.remote.inference.sambanova_openai_compat.config import SambaNovaCompatConfig +from llama_stack.providers.remote.inference.together.models import ( + MODEL_ENTRIES as TOGETHER_MODEL_ENTRIES, +) +from llama_stack.providers.remote.inference.together_openai_compat.config import TogetherCompatConfig +from llama_stack.providers.remote.vector_io.chroma.config import ChromaVectorIOConfig +from llama_stack.providers.remote.vector_io.pgvector.config import ( + PGVectorVectorIOConfig, +) +from llama_stack.templates.template import ( + DistributionTemplate, + RunConfigSettings, + get_model_registry, +) + + +def get_inference_providers() -> Tuple[List[Provider], List[ModelInput]]: + # in this template, we allow each API key to be optional + providers = [ + ( + "openai", + OPENAI_MODEL_ENTRIES, + OpenAIConfig.sample_run_config(api_key="${env.OPENAI_API_KEY:}"), + ), + ( + "fireworks-openai-compat", + FIREWORKS_MODEL_ENTRIES, + FireworksCompatConfig.sample_run_config(api_key="${env.FIREWORKS_API_KEY:}"), + ), + ( + "together-openai-compat", + TOGETHER_MODEL_ENTRIES, + TogetherCompatConfig.sample_run_config(api_key="${env.TOGETHER_API_KEY:}"), + ), + ( + "groq-openai-compat", + GROQ_MODEL_ENTRIES, + GroqCompatConfig.sample_run_config(api_key="${env.GROQ_API_KEY:}"), + ), + ( + "sambanova-openai-compat", + SAMBANOVA_MODEL_ENTRIES, + SambaNovaCompatConfig.sample_run_config(api_key="${env.SAMBANOVA_API_KEY:}"), + ), + ( + "cerebras-openai-compat", + CEREBRAS_MODEL_ENTRIES, + CerebrasCompatConfig.sample_run_config(api_key="${env.CEREBRAS_API_KEY:}"), + ), + ] + inference_providers = [] + available_models = {} + for provider_id, model_entries, config in providers: + inference_providers.append( + Provider( + provider_id=provider_id, + provider_type=f"remote::{provider_id}", + config=config, + ) + ) + available_models[provider_id] = model_entries + return inference_providers, available_models + + +def get_distribution_template() -> DistributionTemplate: + inference_providers, available_models = get_inference_providers() + providers = { + "inference": ([p.provider_type for p in inference_providers] + ["inline::sentence-transformers"]), + "vector_io": ["inline::sqlite-vec", "remote::chromadb", "remote::pgvector"], + "safety": ["inline::llama-guard"], + "agents": ["inline::meta-reference"], + "telemetry": ["inline::meta-reference"], + "eval": ["inline::meta-reference"], + "datasetio": ["remote::huggingface", "inline::localfs"], + "scoring": ["inline::basic", "inline::llm-as-judge", "inline::braintrust"], + "tool_runtime": [ + "remote::brave-search", + "remote::tavily-search", + "inline::code-interpreter", + "inline::rag-runtime", + "remote::model-context-protocol", + ], + } + name = "verification" + + vector_io_providers = [ + Provider( + provider_id="sqlite-vec", + provider_type="inline::sqlite-vec", + config=SQLiteVectorIOConfig.sample_run_config(f"~/.llama/distributions/{name}"), + ), + Provider( + provider_id="${env.ENABLE_CHROMADB+chromadb}", + provider_type="remote::chromadb", + config=ChromaVectorIOConfig.sample_run_config(url="${env.CHROMADB_URL:}"), + ), + Provider( + provider_id="${env.ENABLE_PGVECTOR+pgvector}", + provider_type="remote::pgvector", + config=PGVectorVectorIOConfig.sample_run_config( + db="${env.PGVECTOR_DB:}", + user="${env.PGVECTOR_USER:}", + password="${env.PGVECTOR_PASSWORD:}", + ), + ), + ] + embedding_provider = Provider( + provider_id="sentence-transformers", + provider_type="inline::sentence-transformers", + config=SentenceTransformersInferenceConfig.sample_run_config(), + ) + + default_tool_groups = [ + ToolGroupInput( + toolgroup_id="builtin::websearch", + provider_id="tavily-search", + ), + ToolGroupInput( + toolgroup_id="builtin::rag", + provider_id="rag-runtime", + ), + ToolGroupInput( + toolgroup_id="builtin::code_interpreter", + provider_id="code-interpreter", + ), + ] + embedding_model = ModelInput( + model_id="all-MiniLM-L6-v2", + provider_id=embedding_provider.provider_id, + model_type=ModelType.embedding, + metadata={ + "embedding_dimension": 384, + }, + ) + + default_models = get_model_registry(available_models) + return DistributionTemplate( + name=name, + distro_type="self_hosted", + description="Distribution for running e2e tests in CI", + container_image=None, + template_path=None, + providers=providers, + available_models_by_provider=available_models, + run_configs={ + "run.yaml": RunConfigSettings( + provider_overrides={ + "inference": inference_providers + [embedding_provider], + "vector_io": vector_io_providers, + }, + default_models=default_models + [embedding_model], + default_tool_groups=default_tool_groups, + default_shields=[ShieldInput(shield_id="meta-llama/Llama-Guard-3-8B")], + ), + }, + run_config_env_vars={ + "LLAMA_STACK_PORT": ( + "8321", + "Port for the Llama Stack distribution server", + ), + "FIREWORKS_API_KEY": ( + "", + "Fireworks API Key", + ), + "OPENAI_API_KEY": ( + "", + "OpenAI API Key", + ), + }, + ) diff --git a/tests/integration/inference/test_text_inference.py b/tests/integration/inference/test_text_inference.py index 33ce07304..c8cceb0eb 100644 --- a/tests/integration/inference/test_text_inference.py +++ b/tests/integration/inference/test_text_inference.py @@ -6,6 +6,7 @@ import os +from time import sleep import pytest from pydantic import BaseModel @@ -23,11 +24,15 @@ def skip_if_model_doesnt_support_completion(client_with_models, model_id): provider_id = models[model_id].provider_id providers = {p.provider_id: p for p in client_with_models.providers.list()} provider = providers[provider_id] - if provider.provider_type in ( - "remote::openai", - "remote::anthropic", - "remote::gemini", - "remote::groq", + if ( + provider.provider_type + in ( + "remote::openai", + "remote::anthropic", + "remote::gemini", + "remote::groq", + ) + or "openai-compat" in provider.provider_type ): pytest.skip(f"Model {model_id} hosted by {provider.provider_type} doesn't support completion") @@ -514,7 +519,7 @@ def test_text_chat_completion_tool_calling_tools_not_in_request( ) def test_text_chat_completion_with_multi_turn_tool_calling(client_with_models, text_model_id, test_case): """This test tests the model's tool calling loop in various scenarios""" - if "llama-4" not in text_model_id.lower(): + if "llama-4" not in text_model_id.lower() and "llama4" not in text_model_id.lower(): pytest.xfail("Not tested for non-llama4 models yet") tc = TestCase(test_case) @@ -545,7 +550,7 @@ def test_text_chat_completion_with_multi_turn_tool_calling(client_with_models, t ) op_msg = response.completion_message messages.append(op_msg.model_dump()) - # pprint(op_msg) + # print(op_msg) assert op_msg.role == "assistant" expected = tc["expected"].pop(0) @@ -568,3 +573,6 @@ def test_text_chat_completion_with_multi_turn_tool_calling(client_with_models, t actual_answer = op_msg.content.lower() # pprint(actual_answer) assert expected["answer"] in actual_answer + + # sleep to avoid rate limit + sleep(1) diff --git a/tests/integration/inference/test_vision_inference.py b/tests/integration/inference/test_vision_inference.py index a4fd5bcc6..d47dd3d64 100644 --- a/tests/integration/inference/test_vision_inference.py +++ b/tests/integration/inference/test_vision_inference.py @@ -76,8 +76,9 @@ def multi_image_data(): @pytest.mark.parametrize("stream", [True, False]) def test_image_chat_completion_multiple_images(client_with_models, vision_model_id, multi_image_data, stream): - if "llama-4" not in vision_model_id.lower() and "gpt-4o" not in vision_model_id.lower(): - pytest.skip("Skip for non-llama4, gpt4o models") + supported_models = ["llama-4", "gpt-4o", "llama4"] + if not any(model in vision_model_id.lower() for model in supported_models): + pytest.skip(f"Skip for non-supported model: {vision_model_id}") messages = [ { diff --git a/tests/integration/test_cases/inference/chat_completion.json b/tests/integration/test_cases/inference/chat_completion.json index c84d29e64..01956bd59 100644 --- a/tests/integration/test_cases/inference/chat_completion.json +++ b/tests/integration/test_cases/inference/chat_completion.json @@ -220,7 +220,7 @@ "description": "Availability status of the product." }, "tags": { - "param_type": "list", + "param_type": "list[str]", "description": "List of product tags" } } @@ -294,7 +294,7 @@ "description": "Location of the event" }, "participants": { - "param_type": "list", + "param_type": "list[str]", "description": "List of participant names" } } From c6e93e32f62cbe8e67bc56e0822fc2ff1b04f48b Mon Sep 17 00:00:00 2001 From: Michael Clifford Date: Tue, 8 Apr 2025 03:46:13 -0400 Subject: [PATCH 08/24] feat: Updated playground rag to use session id for persistent conversation (#1870) # What does this PR do? This PR updates the [playground RAG example](llama_stack/distribution/ui/page/playground/rag.py) so that the agent is able to use its builtin conversation history. Here we are using streamlit's `cache_resource` functionality to prevent the agent from re-initializing after every interaction as well as storing its session_id in the `session_state`. This allows the agent in the RAG example to behave more closely to how it works using the python-client directly. [//]: # (If resolving an issue, uncomment and update the line below) Closes #1869 ## Test Plan Without these changes, if you ask it "What is 2 + 2"? followed by the question "What did I just ask?" It will provide an obviously incorrect answer. With these changes, you can ask the same series of questions and it will provide the correct answer. [//]: # (## Documentation) Signed-off-by: Michael Clifford --- .../distribution/ui/page/playground/rag.py | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/llama_stack/distribution/ui/page/playground/rag.py b/llama_stack/distribution/ui/page/playground/rag.py index fcd0f908e..bb31bd2a7 100644 --- a/llama_stack/distribution/ui/page/playground/rag.py +++ b/llama_stack/distribution/ui/page/playground/rag.py @@ -4,6 +4,8 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. +import uuid + import streamlit as st from llama_stack_client import Agent, AgentEventLogger, RAGDocument @@ -102,8 +104,8 @@ def rag_chat_page(): # Add clear chat button to sidebar if st.button("Clear Chat", use_container_width=True): - st.session_state.messages = [] - st.rerun() + st.session_state.clear() + st.cache_resource.clear() # Chat Interface if "messages" not in st.session_state: @@ -123,23 +125,31 @@ def rag_chat_page(): else: strategy = {"type": "greedy"} - agent = Agent( - llama_stack_api.client, - model=selected_model, - instructions=system_prompt, - sampling_params={ - "strategy": strategy, - }, - tools=[ - dict( - name="builtin::rag/knowledge_search", - args={ - "vector_db_ids": list(selected_vector_dbs), - }, - ) - ], - ) - session_id = agent.create_session("rag-session") + @st.cache_resource + def create_agent(): + return Agent( + llama_stack_api.client, + model=selected_model, + instructions=system_prompt, + sampling_params={ + "strategy": strategy, + }, + tools=[ + dict( + name="builtin::rag/knowledge_search", + args={ + "vector_db_ids": list(selected_vector_dbs), + }, + ) + ], + ) + + agent = create_agent() + + if "agent_session_id" not in st.session_state: + st.session_state["agent_session_id"] = agent.create_session(session_name=f"rag_demo_{uuid.uuid4()}") + + session_id = st.session_state["agent_session_id"] # Chat input if prompt := st.chat_input("Ask a question about your documents"): From 031a40bec0ccd2ff66967c40037726dde7055127 Mon Sep 17 00:00:00 2001 From: ehhuang Date: Tue, 8 Apr 2025 09:07:25 -0700 Subject: [PATCH 09/24] fix: type (#1898) # What does this PR do? ## Test Plan --- llama_stack/templates/verification/verification.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/llama_stack/templates/verification/verification.py b/llama_stack/templates/verification/verification.py index 7ef8d6a96..e6f74aad8 100644 --- a/llama_stack/templates/verification/verification.py +++ b/llama_stack/templates/verification/verification.py @@ -4,7 +4,7 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -from typing import List, Tuple +from typing import Dict, List, Tuple from llama_stack.apis.models.models import ModelType from llama_stack.distribution.datatypes import ( @@ -43,6 +43,7 @@ from llama_stack.providers.remote.vector_io.chroma.config import ChromaVectorIOC from llama_stack.providers.remote.vector_io.pgvector.config import ( PGVectorVectorIOConfig, ) +from llama_stack.providers.utils.inference.model_registry import ProviderModelEntry from llama_stack.templates.template import ( DistributionTemplate, RunConfigSettings, @@ -50,7 +51,7 @@ from llama_stack.templates.template import ( ) -def get_inference_providers() -> Tuple[List[Provider], List[ModelInput]]: +def get_inference_providers() -> Tuple[List[Provider], Dict[str, List[ProviderModelEntry]]]: # in this template, we allow each API key to be optional providers = [ ( From 0431a6e90b5e1e325eb874a38b78b34cee9ae611 Mon Sep 17 00:00:00 2001 From: wesley chun Date: Tue, 8 Apr 2025 11:42:47 -0700 Subject: [PATCH 10/24] docs: colorize Discord badge & add icon in README (#1865) Update "chat" badge on README to make it more visible for visitors; changing the look from ![image](https://github.com/user-attachments/assets/630be671-a937-4841-8009-93e8eea1cbe1) ... to ... ![image](https://github.com/user-attachments/assets/cfcb946a-e266-48da-bd50-c994cf1e3a9d) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0a741f916..617e5117b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![PyPI version](https://img.shields.io/pypi/v/llama_stack.svg)](https://pypi.org/project/llama_stack/) [![PyPI - Downloads](https://img.shields.io/pypi/dm/llama-stack)](https://pypi.org/project/llama-stack/) [![License](https://img.shields.io/pypi/l/llama_stack.svg)](https://github.com/meta-llama/llama-stack/blob/main/LICENSE) -[![Discord](https://img.shields.io/discord/1257833999603335178)](https://discord.gg/llama-stack) +[![Discord](https://img.shields.io/discord/1257833999603335178?color=6A7EC2&logo=discord&logoColor=ffffff)](https://discord.gg/llama-stack) [![Unit Tests](https://github.com/meta-llama/llama-stack/actions/workflows/unit-tests.yml/badge.svg?branch=main)](https://github.com/meta-llama/llama-stack/actions/workflows/unit-tests.yml?query=branch%3Amain) [![Integration Tests](https://github.com/meta-llama/llama-stack/actions/workflows/integration-tests.yml/badge.svg?branch=main)](https://github.com/meta-llama/llama-stack/actions/workflows/integration-tests.yml?query=branch%3Amain) From 7d9adf22adc14a5ce5a639f0b6cbd3ba269e7ca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Wed, 9 Apr 2025 03:54:00 +0200 Subject: [PATCH 11/24] refactor: move missing tests to test directory (#1892) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the test_context.py under the main tests directory, and fix the code. The problem was that the function captures the initial values of the context variables and then restores those same initial values before each iteration. This means that any modifications made to the context variables during iteration are lost when the next iteration starts. Error was: ``` ====================================================== FAILURES ======================================================= ______________________________________ test_preserve_contexts_across_event_loops ______________________________________ @pytest.mark.asyncio async def test_preserve_contexts_across_event_loops(): """ Test that context variables are preserved across event loop boundaries with nested generators. This simulates the real-world scenario where: 1. A new event loop is created for each streaming request 2. The async generator runs inside that loop 3. There are multiple levels of nested generators 4. Context needs to be preserved across these boundaries """ # Create context variables request_id = ContextVar("request_id", default=None) user_id = ContextVar("user_id", default=None) # Set initial values # Results container to verify values across thread boundaries results = [] # Inner-most generator (level 2) async def inner_generator(): # Should have the context from the outer scope yield (1, request_id.get(), user_id.get()) # Modify one context variable user_id.set("user-modified") # Should reflect the modification yield (2, request_id.get(), user_id.get()) # Middle generator (level 1) async def middle_generator(): inner_gen = inner_generator() # Forward the first yield from inner item = await inner_gen.__anext__() yield item # Forward the second yield from inner item = await inner_gen.__anext__() yield item request_id.set("req-modified") # Add our own yield with both modified variables yield (3, request_id.get(), user_id.get()) # Function to run in a separate thread with a new event loop def run_in_new_loop(): # Create a new event loop for this thread loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: # Outer generator (runs in the new loop) async def outer_generator(): request_id.set("req-12345") user_id.set("user-6789") # Wrap the middle generator wrapped_gen = preserve_contexts_async_generator(middle_generator(), [request_id, user_id]) # Process all items from the middle generator async for item in wrapped_gen: # Store results for verification results.append(item) # Run the outer generator in the new loop loop.run_until_complete(outer_generator()) finally: loop.close() # Run the generator chain in a separate thread with a new event loop with ThreadPoolExecutor(max_workers=1) as executor: future = executor.submit(run_in_new_loop) future.result() # Wait for completion # Verify the results assert len(results) == 3 # First yield should have original values assert results[0] == (1, "req-12345", "user-6789") # Second yield should have modified user_id assert results[1] == (2, "req-12345", "user-modified") # Third yield should have both modified values > assert results[2] == (3, "req-modified", "user-modified") E AssertionError: assert (3, 'req-modified', 'user-6789') == (3, 'req-modified', 'user-modified') E E At index 2 diff: 'user-6789' != 'user-modified' E E Full diff: E ( E 3, E 'req-modified', E - 'user-modified', E + 'user-6789', E ) tests/unit/distribution/test_context.py:155: AssertionError -------------------------------------------------- Captured log call -------------------------------------------------- ERROR asyncio:base_events.py:1758 Task was destroyed but it is pending! task: ()>> ================================================== warnings summary =================================================== .venv/lib/python3.10/site-packages/pydantic/fields.py:1042 /Users/leseb/Documents/AI/llama-stack/.venv/lib/python3.10/site-packages/pydantic/fields.py:1042: PydanticDeprecatedSince20: Using extra keyword arguments on `Field` is deprecated and will be removed. Use `json_schema_extra` instead. (Extra keys: 'contentEncoding'). Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.10/migration/ warn( -- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html =============================================== short test summary info =============================================== FAILED tests/unit/distribution/test_context.py::test_preserve_contexts_across_event_loops - AssertionError: assert (3, 'req-modified', 'user-6789') == (3, 'req-modified', 'user-modified') At index 2 diff: 'user-6789' != 'user-modified' Full diff: ( 3, 'req-modified', - 'user-modified', + 'user-6789', ) ``` [//]: # (## Documentation) Signed-off-by: Sébastien Han --- llama_stack/distribution/utils/context.py | 5 +++++ .../utils/tests => tests/unit/distribution}/test_context.py | 0 2 files changed, 5 insertions(+) rename {llama_stack/distribution/utils/tests => tests/unit/distribution}/test_context.py (100%) diff --git a/llama_stack/distribution/utils/context.py b/llama_stack/distribution/utils/context.py index fcc72161d..c34079ac6 100644 --- a/llama_stack/distribution/utils/context.py +++ b/llama_stack/distribution/utils/context.py @@ -29,6 +29,11 @@ def preserve_contexts_async_generator( context_var.set(initial_context_values[context_var.name]) item = await gen.__anext__() + + # Update our tracked values with any changes made during this iteration + for context_var in context_vars: + initial_context_values[context_var.name] = context_var.get() + yield item except StopAsyncIteration: diff --git a/llama_stack/distribution/utils/tests/test_context.py b/tests/unit/distribution/test_context.py similarity index 100% rename from llama_stack/distribution/utils/tests/test_context.py rename to tests/unit/distribution/test_context.py From bcbc56baa2368f18823cd507c7d68c6fec5a768a Mon Sep 17 00:00:00 2001 From: ehhuang Date: Tue, 8 Apr 2025 21:21:38 -0700 Subject: [PATCH 12/24] feat: adds test suite to verify provider's OAI compat endpoints (#1901) # What does this PR do? ## Test Plan pytest verifications/openai/test_chat_completion.py --provider together --- tests/verifications/README.md | 65 + tests/verifications/REPORT.md | 88 + tests/verifications/__init__.py | 5 + tests/verifications/conftest.py | 28 + tests/verifications/generate_report.py | 485 +++ tests/verifications/openai/__init__.py | 5 + .../verifications/openai/fixtures/__init__.py | 5 + .../verifications/openai/fixtures/fixtures.py | 97 + tests/verifications/openai/fixtures/load.py | 16 + .../fixtures/test_cases/chat_completion.yaml | 162 + .../openai/test_chat_completion.py | 202 ++ .../test_results/fireworks_1744154308.json | 2744 ++++++++++++++++ .../test_results/openai_1744154522.json | 2672 ++++++++++++++++ .../test_results/together_1744154399.json | 2830 +++++++++++++++++ 14 files changed, 9404 insertions(+) create mode 100644 tests/verifications/README.md create mode 100644 tests/verifications/REPORT.md create mode 100644 tests/verifications/__init__.py create mode 100644 tests/verifications/conftest.py create mode 100755 tests/verifications/generate_report.py create mode 100644 tests/verifications/openai/__init__.py create mode 100644 tests/verifications/openai/fixtures/__init__.py create mode 100644 tests/verifications/openai/fixtures/fixtures.py create mode 100644 tests/verifications/openai/fixtures/load.py create mode 100644 tests/verifications/openai/fixtures/test_cases/chat_completion.yaml create mode 100644 tests/verifications/openai/test_chat_completion.py create mode 100644 tests/verifications/test_results/fireworks_1744154308.json create mode 100644 tests/verifications/test_results/openai_1744154522.json create mode 100644 tests/verifications/test_results/together_1744154399.json diff --git a/tests/verifications/README.md b/tests/verifications/README.md new file mode 100644 index 000000000..986ff1087 --- /dev/null +++ b/tests/verifications/README.md @@ -0,0 +1,65 @@ +# Llama Stack Verifications + +Llama Stack Verifications provide standardized test suites to ensure API compatibility and behavior consistency across different LLM providers. These tests help verify that different models and providers implement the expected interfaces and behaviors correctly. + +## Overview + +This framework allows you to run the same set of verification tests against different LLM providers' OpenAI-compatible endpoints (Fireworks, Together, Groq, Cerebras, etc., and OpenAI itself) to ensure they meet the expected behavior and interface standards. + +## Features + +The verification suite currently tests: + +- Basic chat completions (streaming and non-streaming) +- Image input capabilities +- Structured JSON output formatting +- Tool calling functionality + +## Running Tests + +To run the verification tests, use pytest with the following parameters: + +```bash +cd llama-stack +pytest tests/verifications/openai --provider= +``` + +Example: +```bash +# Run all tests +pytest tests/verifications/openai --provider=together + +# Only run tests with Llama 4 models +pytest tests/verifications/openai --provider=together -k 'Llama-4' +``` + +### Parameters + +- `--provider`: The provider name (openai, fireworks, together, groq, cerebras, etc.) +- `--base-url`: The base URL for the provider's API (optional - defaults to the standard URL for the specified provider) +- `--api-key`: Your API key for the provider (optional - defaults to the standard API_KEY name for the specified provider) + +## Supported Providers + +The verification suite currently supports: +- OpenAI +- Fireworks +- Together +- Groq +- Cerebras + +## Adding New Test Cases + +To add new test cases, create appropriate JSON files in the `openai/fixtures/test_cases/` directory following the existing patterns. + + +## Structure + +- `__init__.py` - Marks the directory as a Python package +- `conftest.py` - Global pytest configuration and fixtures +- `openai/` - Tests specific to OpenAI-compatible APIs + - `fixtures/` - Test fixtures and utilities + - `fixtures.py` - Provider-specific fixtures + - `load.py` - Utilities for loading test cases + - `test_cases/` - JSON test case definitions + - `test_chat_completion.py` - Tests for chat completion APIs diff --git a/tests/verifications/REPORT.md b/tests/verifications/REPORT.md new file mode 100644 index 000000000..d5715ae21 --- /dev/null +++ b/tests/verifications/REPORT.md @@ -0,0 +1,88 @@ +# Test Results Report + +*Generated on: 2025-04-08 21:14:02* + +*This report was generated by running `python tests/verifications/generate_report.py`* + +## Legend + +- ✅ - Test passed +- ❌ - Test failed +- ⚪ - Test not applicable or not run for this model + + +## Summary + +| Provider | Pass Rate | Tests Passed | Total Tests | +| --- | --- | --- | --- | +| Together | 67.7% | 21 | 31 | +| Fireworks | 90.3% | 28 | 31 | +| Openai | 100.0% | 22 | 22 | + + + +## Together + +*Tests run on: 2025-04-08 16:19:59* + +```bash +pytest tests/verifications/openai/test_chat_completion.py --provider=together -v +``` + +| Test | Llama-3.3-70B-Instruct | Llama-4-Maverick-17B-128E-Instruct | Llama-4-Scout-17B-16E-Instruct | +| --- | --- | --- | --- | +| test_chat_non_streaming_basic (case 0) | ✅ | ✅ | ✅ | +| test_chat_non_streaming_basic (case 1) | ✅ | ✅ | ✅ | +| test_chat_non_streaming_image (case 0) | ⚪ | ✅ | ✅ | +| test_chat_non_streaming_structured_output (case 0) | ✅ | ✅ | ✅ | +| test_chat_non_streaming_structured_output (case 1) | ✅ | ✅ | ✅ | +| test_chat_non_streaming_tool_calling (case 0) | ✅ | ✅ | ✅ | +| test_chat_streaming_basic (case 0) | ✅ | ❌ | ❌ | +| test_chat_streaming_basic (case 1) | ✅ | ❌ | ❌ | +| test_chat_streaming_image (case 0) | ⚪ | ❌ | ❌ | +| test_chat_streaming_structured_output (case 0) | ✅ | ❌ | ❌ | +| test_chat_streaming_structured_output (case 1) | ✅ | ❌ | ❌ | + +## Fireworks + +*Tests run on: 2025-04-08 16:18:28* + +```bash +pytest tests/verifications/openai/test_chat_completion.py --provider=fireworks -v +``` + +| Test | Llama-3.3-70B-Instruct | Llama-4-Maverick-17B-128E-Instruct | Llama-4-Scout-17B-16E-Instruct | +| --- | --- | --- | --- | +| test_chat_non_streaming_basic (case 0) | ✅ | ✅ | ✅ | +| test_chat_non_streaming_basic (case 1) | ✅ | ✅ | ✅ | +| test_chat_non_streaming_image (case 0) | ⚪ | ✅ | ✅ | +| test_chat_non_streaming_structured_output (case 0) | ✅ | ✅ | ✅ | +| test_chat_non_streaming_structured_output (case 1) | ✅ | ✅ | ✅ | +| test_chat_non_streaming_tool_calling (case 0) | ✅ | ❌ | ❌ | +| test_chat_streaming_basic (case 0) | ✅ | ✅ | ✅ | +| test_chat_streaming_basic (case 1) | ✅ | ✅ | ✅ | +| test_chat_streaming_image (case 0) | ⚪ | ✅ | ✅ | +| test_chat_streaming_structured_output (case 0) | ✅ | ✅ | ✅ | +| test_chat_streaming_structured_output (case 1) | ❌ | ✅ | ✅ | + +## Openai + +*Tests run on: 2025-04-08 16:22:02* + +```bash +pytest tests/verifications/openai/test_chat_completion.py --provider=openai -v +``` + +| Test | gpt-4o | gpt-4o-mini | +| --- | --- | --- | +| test_chat_non_streaming_basic (case 0) | ✅ | ✅ | +| test_chat_non_streaming_basic (case 1) | ✅ | ✅ | +| test_chat_non_streaming_image (case 0) | ✅ | ✅ | +| test_chat_non_streaming_structured_output (case 0) | ✅ | ✅ | +| test_chat_non_streaming_structured_output (case 1) | ✅ | ✅ | +| test_chat_non_streaming_tool_calling (case 0) | ✅ | ✅ | +| test_chat_streaming_basic (case 0) | ✅ | ✅ | +| test_chat_streaming_basic (case 1) | ✅ | ✅ | +| test_chat_streaming_image (case 0) | ✅ | ✅ | +| test_chat_streaming_structured_output (case 0) | ✅ | ✅ | +| test_chat_streaming_structured_output (case 1) | ✅ | ✅ | diff --git a/tests/verifications/__init__.py b/tests/verifications/__init__.py new file mode 100644 index 000000000..756f351d8 --- /dev/null +++ b/tests/verifications/__init__.py @@ -0,0 +1,5 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. diff --git a/tests/verifications/conftest.py b/tests/verifications/conftest.py new file mode 100644 index 000000000..08967e834 --- /dev/null +++ b/tests/verifications/conftest.py @@ -0,0 +1,28 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + + +def pytest_addoption(parser): + parser.addoption( + "--base-url", + action="store", + help="Base URL for OpenAI compatible API", + ) + parser.addoption( + "--api-key", + action="store", + help="API key", + ) + parser.addoption( + "--provider", + action="store", + help="Provider to use for testing", + ) + + +pytest_plugins = [ + "tests.verifications.openai.fixtures.fixtures", +] diff --git a/tests/verifications/generate_report.py b/tests/verifications/generate_report.py new file mode 100755 index 000000000..98a5930da --- /dev/null +++ b/tests/verifications/generate_report.py @@ -0,0 +1,485 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +""" +Test Report Generator + +Requirements: + pip install pytest-json-report + +Usage: + # Generate a report using existing test results + python tests/verifications/generate_report.py + + # Run tests and generate a report + python tests/verifications/generate_report.py --run-tests + + # Run tests for specific providers + python tests/verifications/generate_report.py --run-tests --providers fireworks openai + + # Save the report to a custom location + python tests/verifications/generate_report.py --output custom_report.md + + # Clean up old test result files + python tests/verifications/generate_report.py --cleanup +""" + +import argparse +import json +import os +import re +import subprocess +import time +from collections import defaultdict +from pathlib import Path + +# Define the root directory for test results +RESULTS_DIR = Path(__file__).parent / "test_results" +RESULTS_DIR.mkdir(exist_ok=True) + +# Maximum number of test result files to keep per provider +MAX_RESULTS_PER_PROVIDER = 1 + +# Custom order of providers +PROVIDER_ORDER = ["together", "fireworks", "groq", "cerebras", "openai"] + +# Dictionary to store providers and their models (will be populated dynamically) +PROVIDERS = defaultdict(set) + +# Tests will be dynamically extracted from results +ALL_TESTS = set() + + +def run_tests(provider): + """Run pytest for a specific provider and save results""" + print(f"Running tests for provider: {provider}") + + timestamp = int(time.time()) + result_file = RESULTS_DIR / f"{provider}_{timestamp}.json" + temp_json_file = RESULTS_DIR / f"temp_{provider}_{timestamp}.json" + + # Run pytest with JSON output + cmd = [ + "python", + "-m", + "pytest", + "tests/verifications/openai/test_chat_completion.py", + f"--provider={provider}", + "-v", + "--json-report", + f"--json-report-file={temp_json_file}", + ] + + try: + result = subprocess.run(cmd, capture_output=True, text=True) + print(f"Pytest exit code: {result.returncode}") + + # Check if the JSON file was created + if temp_json_file.exists(): + # Read the JSON file and save it to our results format + with open(temp_json_file, "r") as f: + test_results = json.load(f) + + # Save results to our own format with a trailing newline + with open(result_file, "w") as f: + json.dump(test_results, f, indent=2) + f.write("\n") # Add a trailing newline for precommit + + # Clean up temp file + temp_json_file.unlink() + + print(f"Test results saved to {result_file}") + return result_file + else: + print(f"Error: JSON report file not created for {provider}") + print(f"Command stdout: {result.stdout}") + print(f"Command stderr: {result.stderr}") + return None + except Exception as e: + print(f"Error running tests for {provider}: {e}") + return None + + +def parse_results(result_file): + """Parse the test results file and extract pass/fail by model and test""" + if not os.path.exists(result_file): + print(f"Results file does not exist: {result_file}") + return {} + + with open(result_file, "r") as f: + results = json.load(f) + + # Initialize results dictionary + parsed_results = defaultdict(lambda: defaultdict(dict)) + provider = os.path.basename(result_file).split("_")[0] + + # Debug: Print summary of test results + print(f"Test results summary for {provider}:") + print(f"Total tests: {results.get('summary', {}).get('total', 0)}") + print(f"Passed: {results.get('summary', {}).get('passed', 0)}") + print(f"Failed: {results.get('summary', {}).get('failed', 0)}") + print(f"Error: {results.get('summary', {}).get('error', 0)}") + print(f"Skipped: {results.get('summary', {}).get('skipped', 0)}") + + # Extract test results + if "tests" not in results or not results["tests"]: + print(f"No test results found in {result_file}") + return parsed_results + + # Map for normalizing model names + model_name_map = { + "Llama-3.3-8B-Instruct": "Llama-3.3-8B-Instruct", + "Llama-3.3-70B-Instruct": "Llama-3.3-70B-Instruct", + "Llama-3.2-11B-Vision-Instruct": "Llama-3.2-11B-Vision-Instruct", + "Llama-4-Scout-17B-16E": "Llama-4-Scout-17B-16E-Instruct", + "Llama-4-Scout-17B-16E-Instruct": "Llama-4-Scout-17B-16E-Instruct", + "Llama-4-Maverick-17B-128E": "Llama-4-Maverick-17B-128E-Instruct", + "Llama-4-Maverick-17B-128E-Instruct": "Llama-4-Maverick-17B-128E-Instruct", + "gpt-4o": "gpt-4o", + "gpt-4o-mini": "gpt-4o-mini", + } + + # Keep track of all models found for this provider + provider_models = set() + + # Track all unique test cases for each base test + test_case_counts = defaultdict(int) + + # First pass: count the number of cases for each test + for test in results["tests"]: + test_id = test.get("nodeid", "") + + if "call" in test: + test_name = test_id.split("::")[1].split("[")[0] + input_output_match = re.search(r"\[input_output(\d+)-", test_id) + if input_output_match: + test_case_counts[test_name] += 1 + + # Second pass: process the tests with case numbers only for tests with multiple cases + for test in results["tests"]: + test_id = test.get("nodeid", "") + outcome = test.get("outcome", "") + + # Only process tests that have been executed (not setup errors) + if "call" in test: + # Regular test that actually ran + test_name = test_id.split("::")[1].split("[")[0] + + # Extract input_output parameter to differentiate between test cases + input_output_match = re.search(r"\[input_output(\d+)-", test_id) + input_output_index = input_output_match.group(1) if input_output_match else "" + + # Create a more detailed test name with case number only if there are multiple cases + detailed_test_name = test_name + if input_output_index and test_case_counts[test_name] > 1: + detailed_test_name = f"{test_name} (case {input_output_index})" + + # Track all unique test names + ALL_TESTS.add(detailed_test_name) + + # Extract model name from test_id using a more robust pattern + model_match = re.search(r"\[input_output\d+-([^\]]+)\]", test_id) + if model_match: + raw_model = model_match.group(1) + model = model_name_map.get(raw_model, raw_model) + + # Add to set of known models for this provider + provider_models.add(model) + + # Also update the global PROVIDERS dictionary + PROVIDERS[provider].add(model) + + # Store the result + if outcome == "passed": + parsed_results[provider][model][detailed_test_name] = True + else: + parsed_results[provider][model][detailed_test_name] = False + + print(f"Parsed test result: {detailed_test_name} for model {model}: {outcome}") + elif outcome == "error" and "setup" in test and test.get("setup", {}).get("outcome") == "failed": + # This is a setup failure, which likely means a configuration issue + # Extract the base test name and model name + parts = test_id.split("::") + if len(parts) > 1: + test_name = parts[1].split("[")[0] + + # Extract input_output parameter to differentiate between test cases + input_output_match = re.search(r"\[input_output(\d+)-", test_id) + input_output_index = input_output_match.group(1) if input_output_match else "" + + # Create a more detailed test name with case number only if there are multiple cases + detailed_test_name = test_name + if input_output_index and test_case_counts[test_name] > 1: + detailed_test_name = f"{test_name} (case {input_output_index})" + + if detailed_test_name in ALL_TESTS: + # Use a more robust pattern for model extraction + model_match = re.search(r"\[input_output\d+-([^\]]+)\]", test_id) + if model_match: + raw_model = model_match.group(1) + model = model_name_map.get(raw_model, raw_model) + + # Add to set of known models for this provider + provider_models.add(model) + + # Also update the global PROVIDERS dictionary + PROVIDERS[provider].add(model) + + # Mark setup failures as false (failed) + parsed_results[provider][model][detailed_test_name] = False + print(f"Parsed setup failure: {detailed_test_name} for model {model}") + + # Debug: Print parsed results + if not parsed_results[provider]: + print(f"Warning: No test results parsed for provider {provider}") + else: + for model, tests in parsed_results[provider].items(): + print(f"Model {model}: {len(tests)} test results") + + return parsed_results + + +def cleanup_old_results(): + """Clean up old test result files, keeping only the newest N per provider""" + for provider in PROVIDERS.keys(): + # Get all result files for this provider + provider_files = list(RESULTS_DIR.glob(f"{provider}_*.json")) + + # Sort by timestamp (newest first) + provider_files.sort(key=lambda x: int(x.stem.split("_")[1]), reverse=True) + + # Remove old files beyond the max to keep + if len(provider_files) > MAX_RESULTS_PER_PROVIDER: + for old_file in provider_files[MAX_RESULTS_PER_PROVIDER:]: + try: + old_file.unlink() + print(f"Removed old result file: {old_file}") + except Exception as e: + print(f"Error removing file {old_file}: {e}") + + +def get_latest_results_by_provider(): + """Get the latest test result file for each provider""" + provider_results = {} + + # Get all result files + result_files = list(RESULTS_DIR.glob("*.json")) + + # Extract all provider names from filenames + all_providers = set() + for file in result_files: + # File format is provider_timestamp.json + parts = file.stem.split("_") + if len(parts) >= 2: + all_providers.add(parts[0]) + + # Group by provider + for provider in all_providers: + provider_files = [f for f in result_files if f.name.startswith(f"{provider}_")] + + # Sort by timestamp (newest first) + provider_files.sort(key=lambda x: int(x.stem.split("_")[1]), reverse=True) + + if provider_files: + provider_results[provider] = provider_files[0] + + return provider_results + + +def generate_report(results_dict, output_file=None): + """Generate the markdown report""" + if output_file is None: + # Default to creating the report in the same directory as this script + output_file = Path(__file__).parent / "REPORT.md" + else: + output_file = Path(output_file) + + # Get the timestamp from result files + provider_timestamps = {} + provider_results = get_latest_results_by_provider() + for provider, result_file in provider_results.items(): + # Extract timestamp from filename (format: provider_timestamp.json) + try: + timestamp_str = result_file.stem.split("_")[1] + timestamp = int(timestamp_str) + formatted_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp)) + provider_timestamps[provider] = formatted_time + except (IndexError, ValueError): + provider_timestamps[provider] = "Unknown" + + # Convert provider model sets to sorted lists + for provider in PROVIDERS: + PROVIDERS[provider] = sorted(PROVIDERS[provider]) + + # Sort tests alphabetically + sorted_tests = sorted(ALL_TESTS) + + report = ["# Test Results Report\n"] + report.append(f"*Generated on: {time.strftime('%Y-%m-%d %H:%M:%S')}*\n") + report.append("*This report was generated by running `python tests/verifications/generate_report.py`*\n") + + # Icons for pass/fail + pass_icon = "✅" + fail_icon = "❌" + na_icon = "⚪" + + # Add emoji legend + report.append("## Legend\n") + report.append(f"- {pass_icon} - Test passed") + report.append(f"- {fail_icon} - Test failed") + report.append(f"- {na_icon} - Test not applicable or not run for this model") + report.append("\n") + + # Add a summary section + report.append("## Summary\n") + + # Count total tests and passes + total_tests = 0 + passed_tests = 0 + provider_totals = {} + + # Prepare summary data + for provider in PROVIDERS.keys(): + provider_passed = 0 + provider_total = 0 + + if provider in results_dict: + provider_models = PROVIDERS[provider] + for model in provider_models: + if model in results_dict[provider]: + model_results = results_dict[provider][model] + for test in sorted_tests: + if test in model_results: + provider_total += 1 + total_tests += 1 + if model_results[test]: + provider_passed += 1 + passed_tests += 1 + + provider_totals[provider] = (provider_passed, provider_total) + + # Add summary table + report.append("| Provider | Pass Rate | Tests Passed | Total Tests |") + report.append("| --- | --- | --- | --- |") + + # Use the custom order for summary table + for provider in [p for p in PROVIDER_ORDER if p in PROVIDERS]: + passed, total = provider_totals.get(provider, (0, 0)) + pass_rate = f"{(passed / total * 100):.1f}%" if total > 0 else "N/A" + report.append(f"| {provider.capitalize()} | {pass_rate} | {passed} | {total} |") + + # Add providers not in the custom order + for provider in [p for p in PROVIDERS if p not in PROVIDER_ORDER]: + passed, total = provider_totals.get(provider, (0, 0)) + pass_rate = f"{(passed / total * 100):.1f}%" if total > 0 else "N/A" + report.append(f"| {provider.capitalize()} | {pass_rate} | {passed} | {total} |") + + report.append("\n") + + # Process each provider in the custom order, then any additional providers + for provider in sorted( + PROVIDERS.keys(), key=lambda p: (PROVIDER_ORDER.index(p) if p in PROVIDER_ORDER else float("inf"), p) + ): + if not PROVIDERS[provider]: + # Skip providers with no models + continue + + report.append(f"\n## {provider.capitalize()}\n") + + # Add timestamp when test was run + if provider in provider_timestamps: + report.append(f"*Tests run on: {provider_timestamps[provider]}*\n") + + # Add test command for reproducing results + test_cmd = f"pytest tests/verifications/openai/test_chat_completion.py --provider={provider} -v" + report.append(f"```bash\n{test_cmd}\n```\n") + + # Get the relevant models for this provider + provider_models = PROVIDERS[provider] + + # Create table header with models as columns + header = "| Test | " + " | ".join(provider_models) + " |" + separator = "| --- | " + " | ".join(["---"] * len(provider_models)) + " |" + + report.append(header) + report.append(separator) + + # Get results for this provider + provider_results = results_dict.get(provider, {}) + + # Add rows for each test + for test in sorted_tests: + row = f"| {test} |" + + # Add results for each model in this test + for model in provider_models: + if model in provider_results and test in provider_results[model]: + result = pass_icon if provider_results[model][test] else fail_icon + else: + result = na_icon + row += f" {result} |" + + report.append(row) + + # Write to file + with open(output_file, "w") as f: + f.write("\n".join(report)) + f.write("\n") + + print(f"Report generated: {output_file}") + + +def main(): + parser = argparse.ArgumentParser(description="Generate test report") + parser.add_argument("--run-tests", action="store_true", help="Run tests before generating report") + parser.add_argument( + "--providers", + type=str, + nargs="+", + help="Specify providers to test (comma-separated or space-separated, default: all)", + ) + parser.add_argument("--output", type=str, help="Output file location (default: tests/verifications/REPORT.md)") + args = parser.parse_args() + + all_results = {} + + if args.run_tests: + # Get list of available providers from command line or use detected providers + if args.providers: + # Handle both comma-separated and space-separated lists + test_providers = [] + for provider_arg in args.providers: + # Split by comma if commas are present + if "," in provider_arg: + test_providers.extend(provider_arg.split(",")) + else: + test_providers.append(provider_arg) + else: + # Default providers to test + test_providers = PROVIDER_ORDER + + for provider in test_providers: + provider = provider.strip() # Remove any whitespace + result_file = run_tests(provider) + if result_file: + provider_results = parse_results(result_file) + all_results.update(provider_results) + else: + # Use existing results + provider_result_files = get_latest_results_by_provider() + + for result_file in provider_result_files.values(): + provider_results = parse_results(result_file) + all_results.update(provider_results) + + # Generate the report + generate_report(all_results, args.output) + + cleanup_old_results() + + +if __name__ == "__main__": + main() diff --git a/tests/verifications/openai/__init__.py b/tests/verifications/openai/__init__.py new file mode 100644 index 000000000..756f351d8 --- /dev/null +++ b/tests/verifications/openai/__init__.py @@ -0,0 +1,5 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. diff --git a/tests/verifications/openai/fixtures/__init__.py b/tests/verifications/openai/fixtures/__init__.py new file mode 100644 index 000000000..756f351d8 --- /dev/null +++ b/tests/verifications/openai/fixtures/__init__.py @@ -0,0 +1,5 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. diff --git a/tests/verifications/openai/fixtures/fixtures.py b/tests/verifications/openai/fixtures/fixtures.py new file mode 100644 index 000000000..b86de3662 --- /dev/null +++ b/tests/verifications/openai/fixtures/fixtures.py @@ -0,0 +1,97 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +import os + +import pytest +from openai import OpenAI + + +@pytest.fixture +def providers_model_mapping(): + """ + Mapping from model names used in test cases to provider's model names. + """ + return { + "fireworks": { + "Llama-3.3-70B-Instruct": "accounts/fireworks/models/llama-v3p1-70b-instruct", + "Llama-3.2-11B-Vision-Instruct": "accounts/fireworks/models/llama-v3p2-11b-vision-instruct", + "Llama-4-Scout-17B-16E-Instruct": "accounts/fireworks/models/llama4-scout-instruct-basic", + "Llama-4-Maverick-17B-128E-Instruct": "accounts/fireworks/models/llama4-maverick-instruct-basic", + }, + "together": { + "Llama-3.3-70B-Instruct": "meta-llama/Llama-3.3-70B-Instruct-Turbo", + "Llama-3.2-11B-Vision-Instruct": "meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo", + "Llama-4-Scout-17B-16E-Instruct": "meta-llama/Llama-4-Scout-17B-16E-Instruct", + "Llama-4-Maverick-17B-128E-Instruct": "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", + }, + "groq": { + "Llama-3.3-70B-Instruct": "llama-3.3-70b-versatile", + "Llama-3.2-11B-Vision-Instruct": "llama-3.2-11b-vision-preview", + "Llama-4-Scout-17B-16E-Instruct": "llama-4-scout-17b-16e-instruct", + "Llama-4-Maverick-17B-128E-Instruct": "llama-4-maverick-17b-128e-instruct", + }, + "cerebras": { + "Llama-3.3-70B-Instruct": "llama-3.3-70b", + }, + "openai": { + "gpt-4o": "gpt-4o", + "gpt-4o-mini": "gpt-4o-mini", + }, + } + + +@pytest.fixture +def provider_metadata(): + return { + "fireworks": ("https://api.fireworks.ai/inference/v1", "FIREWORKS_API_KEY"), + "together": ("https://api.together.xyz/v1", "TOGETHER_API_KEY"), + "groq": ("https://api.groq.com/openai/v1", "GROQ_API_KEY"), + "cerebras": ("https://api.cerebras.ai/v1", "CEREBRAS_API_KEY"), + "openai": ("https://api.openai.com/v1", "OPENAI_API_KEY"), + } + + +@pytest.fixture +def provider(request, provider_metadata): + provider = request.config.getoption("--provider") + base_url = request.config.getoption("--base-url") + + if provider and base_url and provider_metadata[provider][0] != base_url: + raise ValueError(f"Provider {provider} is not supported for base URL {base_url}") + + if not provider: + if not base_url: + raise ValueError("Provider and base URL are not provided") + for provider, metadata in provider_metadata.items(): + if metadata[0] == base_url: + provider = provider + break + + return provider + + +@pytest.fixture +def base_url(request, provider, provider_metadata): + return request.config.getoption("--base-url") or provider_metadata[provider][0] + + +@pytest.fixture +def api_key(request, provider, provider_metadata): + return request.config.getoption("--api-key") or os.getenv(provider_metadata[provider][1]) + + +@pytest.fixture +def model_mapping(provider, providers_model_mapping): + return providers_model_mapping[provider] + + +@pytest.fixture +def openai_client(base_url, api_key): + return OpenAI( + base_url=base_url, + api_key=api_key, + ) diff --git a/tests/verifications/openai/fixtures/load.py b/tests/verifications/openai/fixtures/load.py new file mode 100644 index 000000000..98580b2a1 --- /dev/null +++ b/tests/verifications/openai/fixtures/load.py @@ -0,0 +1,16 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from pathlib import Path + +import yaml + + +def load_test_cases(name: str): + fixture_dir = Path(__file__).parent / "test_cases" + yaml_path = fixture_dir / f"{name}.yaml" + with open(yaml_path, "r") as f: + return yaml.safe_load(f) diff --git a/tests/verifications/openai/fixtures/test_cases/chat_completion.yaml b/tests/verifications/openai/fixtures/test_cases/chat_completion.yaml new file mode 100644 index 000000000..2c302a704 --- /dev/null +++ b/tests/verifications/openai/fixtures/test_cases/chat_completion.yaml @@ -0,0 +1,162 @@ +test_chat_basic: + test_name: test_chat_basic + test_params: + input_output: + - input: + messages: + - content: Which planet do humans live on? + role: user + output: Earth + - input: + messages: + - content: Which planet has rings around it with a name starting with letter + S? + role: user + output: Saturn + model: + - Llama-3.3-8B-Instruct + - Llama-3.3-70B-Instruct + - Llama-4-Scout-17B-16E + - Llama-4-Scout-17B-16E-Instruct + - Llama-4-Maverick-17B-128E + - Llama-4-Maverick-17B-128E-Instruct + - gpt-4o + - gpt-4o-mini +test_chat_image: + test_name: test_chat_image + test_params: + input_output: + - input: + messages: + - content: + - text: What is in this image? + type: text + - image_url: + url: https://upload.wikimedia.org/wikipedia/commons/f/f7/Llamas%2C_Vernagt-Stausee%2C_Italy.jpg + type: image_url + role: user + output: llama + model: + - Llama-4-Scout-17B-16E + - Llama-4-Scout-17B-16E-Instruct + - Llama-4-Maverick-17B-128E + - Llama-4-Maverick-17B-128E-Instruct + - gpt-4o + - gpt-4o-mini +test_chat_structured_output: + test_name: test_chat_structured_output + test_params: + input_output: + - input: + messages: + - content: Extract the event information. + role: system + - content: Alice and Bob are going to a science fair on Friday. + role: user + response_format: + json_schema: + name: calendar_event + schema: + properties: + date: + title: Date + type: string + name: + title: Name + type: string + participants: + items: + type: string + title: Participants + type: array + required: + - name + - date + - participants + title: CalendarEvent + type: object + type: json_schema + output: valid_calendar_event + - input: + messages: + - content: You are a helpful math tutor. Guide the user through the solution + step by step. + role: system + - content: how can I solve 8x + 7 = -23 + role: user + response_format: + json_schema: + name: math_reasoning + schema: + $defs: + Step: + properties: + explanation: + title: Explanation + type: string + output: + title: Output + type: string + required: + - explanation + - output + title: Step + type: object + properties: + final_answer: + title: Final Answer + type: string + steps: + items: + $ref: '#/$defs/Step' + title: Steps + type: array + required: + - steps + - final_answer + title: MathReasoning + type: object + type: json_schema + output: valid_math_reasoning + model: + - Llama-3.3-8B-Instruct + - Llama-3.3-70B-Instruct + - Llama-4-Scout-17B-16E + - Llama-4-Scout-17B-16E-Instruct + - Llama-4-Maverick-17B-128E + - Llama-4-Maverick-17B-128E-Instruct + - gpt-4o + - gpt-4o-mini +test_tool_calling: + test_name: test_tool_calling + test_params: + input_output: + - input: + messages: + - content: You are a helpful assistant that can use tools to get information. + role: system + - content: What's the weather like in San Francisco? + role: user + tools: + - function: + description: Get current temperature for a given location. + name: get_weather + parameters: + additionalProperties: false + properties: + location: + description: "City and country e.g. Bogot\xE1, Colombia" + type: string + required: + - location + type: object + type: function + output: get_weather_tool_call + model: + - Llama-3.3-70B-Instruct + - Llama-4-Scout-17B-16E + - Llama-4-Scout-17B-16E-Instruct + - Llama-4-Maverick-17B-128E + - Llama-4-Maverick-17B-128E-Instruct + - gpt-4o + - gpt-4o-mini diff --git a/tests/verifications/openai/test_chat_completion.py b/tests/verifications/openai/test_chat_completion.py new file mode 100644 index 000000000..c6a10de7b --- /dev/null +++ b/tests/verifications/openai/test_chat_completion.py @@ -0,0 +1,202 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from typing import Any + +import pytest +from pydantic import BaseModel + +from tests.verifications.openai.fixtures.load import load_test_cases + +chat_completion_test_cases = load_test_cases("chat_completion") + + +@pytest.fixture +def correct_model_name(model, provider, providers_model_mapping): + """Return the provider-specific model name based on the generic model name.""" + mapping = providers_model_mapping[provider] + if model not in mapping: + pytest.skip(f"Provider {provider} does not support model {model}") + return mapping[model] + + +@pytest.mark.parametrize("model", chat_completion_test_cases["test_chat_basic"]["test_params"]["model"]) +@pytest.mark.parametrize( + "input_output", + chat_completion_test_cases["test_chat_basic"]["test_params"]["input_output"], +) +def test_chat_non_streaming_basic(openai_client, input_output, correct_model_name): + response = openai_client.chat.completions.create( + model=correct_model_name, + messages=input_output["input"]["messages"], + stream=False, + ) + assert response.choices[0].message.role == "assistant" + assert input_output["output"].lower() in response.choices[0].message.content.lower() + + +@pytest.mark.parametrize("model", chat_completion_test_cases["test_chat_basic"]["test_params"]["model"]) +@pytest.mark.parametrize( + "input_output", + chat_completion_test_cases["test_chat_basic"]["test_params"]["input_output"], +) +def test_chat_streaming_basic(openai_client, input_output, correct_model_name): + response = openai_client.chat.completions.create( + model=correct_model_name, + messages=input_output["input"]["messages"], + stream=True, + ) + content = "" + for chunk in response: + content += chunk.choices[0].delta.content or "" + + # TODO: add detailed type validation + + assert input_output["output"].lower() in content.lower() + + +@pytest.mark.parametrize("model", chat_completion_test_cases["test_chat_image"]["test_params"]["model"]) +@pytest.mark.parametrize( + "input_output", + chat_completion_test_cases["test_chat_image"]["test_params"]["input_output"], +) +def test_chat_non_streaming_image(openai_client, input_output, correct_model_name): + response = openai_client.chat.completions.create( + model=correct_model_name, + messages=input_output["input"]["messages"], + stream=False, + ) + assert response.choices[0].message.role == "assistant" + assert input_output["output"].lower() in response.choices[0].message.content.lower() + + +@pytest.mark.parametrize("model", chat_completion_test_cases["test_chat_image"]["test_params"]["model"]) +@pytest.mark.parametrize( + "input_output", + chat_completion_test_cases["test_chat_image"]["test_params"]["input_output"], +) +def test_chat_streaming_image(openai_client, input_output, correct_model_name): + response = openai_client.chat.completions.create( + model=correct_model_name, + messages=input_output["input"]["messages"], + stream=True, + ) + content = "" + for chunk in response: + content += chunk.choices[0].delta.content or "" + + # TODO: add detailed type validation + + assert input_output["output"].lower() in content.lower() + + +@pytest.mark.parametrize( + "model", + chat_completion_test_cases["test_chat_structured_output"]["test_params"]["model"], +) +@pytest.mark.parametrize( + "input_output", + chat_completion_test_cases["test_chat_structured_output"]["test_params"]["input_output"], +) +def test_chat_non_streaming_structured_output(openai_client, input_output, correct_model_name): + response = openai_client.chat.completions.create( + model=correct_model_name, + messages=input_output["input"]["messages"], + response_format=input_output["input"]["response_format"], + stream=False, + ) + + assert response.choices[0].message.role == "assistant" + maybe_json_content = response.choices[0].message.content + + validate_structured_output(maybe_json_content, input_output["output"]) + + +@pytest.mark.parametrize( + "model", + chat_completion_test_cases["test_chat_structured_output"]["test_params"]["model"], +) +@pytest.mark.parametrize( + "input_output", + chat_completion_test_cases["test_chat_structured_output"]["test_params"]["input_output"], +) +def test_chat_streaming_structured_output(openai_client, input_output, correct_model_name): + response = openai_client.chat.completions.create( + model=correct_model_name, + messages=input_output["input"]["messages"], + response_format=input_output["input"]["response_format"], + stream=True, + ) + maybe_json_content = "" + for chunk in response: + maybe_json_content += chunk.choices[0].delta.content or "" + validate_structured_output(maybe_json_content, input_output["output"]) + + +@pytest.mark.parametrize( + "model", + chat_completion_test_cases["test_tool_calling"]["test_params"]["model"], +) +@pytest.mark.parametrize( + "input_output", + chat_completion_test_cases["test_tool_calling"]["test_params"]["input_output"], +) +def test_chat_non_streaming_tool_calling(openai_client, input_output, correct_model_name): + response = openai_client.chat.completions.create( + model=correct_model_name, + messages=input_output["input"]["messages"], + tools=input_output["input"]["tools"], + stream=False, + ) + + assert response.choices[0].message.role == "assistant" + assert len(response.choices[0].message.tool_calls) > 0 + assert input_output["output"] == "get_weather_tool_call" + assert response.choices[0].message.tool_calls[0].function.name == "get_weather" + # TODO: add detailed type validation + + +def get_structured_output(maybe_json_content: str, schema_name: str) -> Any | None: + if schema_name == "valid_calendar_event": + + class CalendarEvent(BaseModel): + name: str + date: str + participants: list[str] + + try: + calendar_event = CalendarEvent.model_validate_json(maybe_json_content) + return calendar_event + except Exception: + return None + elif schema_name == "valid_math_reasoning": + + class Step(BaseModel): + explanation: str + output: str + + class MathReasoning(BaseModel): + steps: list[Step] + final_answer: str + + try: + math_reasoning = MathReasoning.model_validate_json(maybe_json_content) + return math_reasoning + except Exception: + return None + + return None + + +def validate_structured_output(maybe_json_content: str, schema_name: str) -> None: + structured_output = get_structured_output(maybe_json_content, schema_name) + assert structured_output is not None + if schema_name == "valid_calendar_event": + assert structured_output.name is not None + assert structured_output.date is not None + assert len(structured_output.participants) == 2 + elif schema_name == "valid_math_reasoning": + assert len(structured_output.final_answer) > 0 diff --git a/tests/verifications/test_results/fireworks_1744154308.json b/tests/verifications/test_results/fireworks_1744154308.json new file mode 100644 index 000000000..691f6e474 --- /dev/null +++ b/tests/verifications/test_results/fireworks_1744154308.json @@ -0,0 +1,2744 @@ +{ + "created": 1744154399.039055, + "duration": 87.73799800872803, + "exitcode": 1, + "root": "/Users/erichuang/projects/llama-stack", + "environment": {}, + "summary": { + "skipped": 52, + "passed": 28, + "failed": 3, + "total": 83, + "collected": 83 + }, + "collectors": [ + { + "nodeid": "", + "outcome": "passed", + "result": [ + { + "nodeid": "tests/verifications/openai/test_chat_completion.py", + "type": "Module" + } + ] + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py", + "outcome": "passed", + "result": [ + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-gpt-4o]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-gpt-4o-mini]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-gpt-4o]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-gpt-4o-mini]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-gpt-4o]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-gpt-4o-mini]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-gpt-4o]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-gpt-4o-mini]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 60 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 60 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 60 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 60 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-gpt-4o]", + "type": "Function", + "lineno": 60 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-gpt-4o-mini]", + "type": "Function", + "lineno": 60 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 75 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 75 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 75 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 75 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-gpt-4o]", + "type": "Function", + "lineno": 75 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-gpt-4o-mini]", + "type": "Function", + "lineno": 75 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-gpt-4o]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-gpt-4o-mini]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-gpt-4o]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-gpt-4o-mini]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-gpt-4o]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-gpt-4o-mini]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-gpt-4o]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-gpt-4o-mini]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 138 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 138 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 138 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 138 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 138 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-gpt-4o]", + "type": "Function", + "lineno": 138 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-gpt-4o-mini]", + "type": "Function", + "lineno": 138 + } + ] + } + ], + "tests": [ + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-3.3-8B-Instruct]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.17320987500716, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider fireworks does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.000177707988768816, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-3.3-70B-Instruct]", + "lineno": 25, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.009193749981932342, + "outcome": "passed" + }, + "call": { + "duration": 1.1473859580000862, + "outcome": "passed" + }, + "teardown": { + "duration": 0.00043337501119822264, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Scout-17B-16E]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.01645291701424867, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider fireworks does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.0002898749662563205, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 25, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.01562033302616328, + "outcome": "passed" + }, + "call": { + "duration": 0.8782661251025274, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0002795408945530653, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Maverick-17B-128E]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.008571124984882772, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider fireworks does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.0003043749602511525, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 25, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.00842841702979058, + "outcome": "passed" + }, + "call": { + "duration": 1.3863223339430988, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0009970410028472543, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-gpt-4o]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-gpt-4o]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.007089875056408346, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider fireworks does not support model gpt-4o')" + }, + "teardown": { + "duration": 0.00017958390526473522, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-gpt-4o-mini]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.005809499998576939, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider fireworks does not support model gpt-4o-mini')" + }, + "teardown": { + "duration": 0.00016495899762958288, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-3.3-8B-Instruct]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.0119722920935601, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider fireworks does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.00016962504014372826, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-3.3-70B-Instruct]", + "lineno": 25, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.005716291954740882, + "outcome": "passed" + }, + "call": { + "duration": 0.6822018750244752, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0005292498972266912, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Scout-17B-16E]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.025827708072029054, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider fireworks does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.000295999925583601, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 25, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.010980832972563803, + "outcome": "passed" + }, + "call": { + "duration": 0.7537062909686938, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0008091670460999012, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Maverick-17B-128E]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.006567832897417247, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider fireworks does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.0001545000122860074, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 25, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.005985083989799023, + "outcome": "passed" + }, + "call": { + "duration": 0.7263387079583481, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0006324589485302567, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-gpt-4o]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-gpt-4o]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.0171962499152869, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider fireworks does not support model gpt-4o')" + }, + "teardown": { + "duration": 0.000780042028054595, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-gpt-4o-mini]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.01365620899014175, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider fireworks does not support model gpt-4o-mini')" + }, + "teardown": { + "duration": 0.00016758404672145844, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-3.3-8B-Instruct]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output0-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.0064070840599015355, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider fireworks does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.0002031669719144702, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-3.3-70B-Instruct]", + "lineno": 40, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_basic[input_output0-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.010951624950394034, + "outcome": "passed" + }, + "call": { + "duration": 0.5433399169705808, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0013178749941289425, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Scout-17B-16E]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output0-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.022056750021874905, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider fireworks does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.0006570409750565886, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 40, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_basic[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.008314333041198552, + "outcome": "passed" + }, + "call": { + "duration": 0.7779882500180975, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0006799160037189722, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Maverick-17B-128E]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output0-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.03601404093205929, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider fireworks does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.000610582996159792, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 40, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_basic[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.014321292052045465, + "outcome": "passed" + }, + "call": { + "duration": 1.0243758750148118, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0010485410457476974, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-gpt-4o]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output0-gpt-4o]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.021133000031113625, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider fireworks does not support model gpt-4o')" + }, + "teardown": { + "duration": 0.0005400830414146185, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-gpt-4o-mini]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output0-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.007212458993308246, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider fireworks does not support model gpt-4o-mini')" + }, + "teardown": { + "duration": 0.00026770797558128834, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-3.3-8B-Instruct]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output1-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.012334750033915043, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider fireworks does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.00042683398351073265, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-3.3-70B-Instruct]", + "lineno": 40, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_basic[input_output1-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.011477917083539069, + "outcome": "passed" + }, + "call": { + "duration": 1.670572166913189, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0005759169580414891, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Scout-17B-16E]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output1-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.024620208074338734, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider fireworks does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.0005166250048205256, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 40, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_basic[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.008708957931958139, + "outcome": "passed" + }, + "call": { + "duration": 0.6654335829662159, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0002927089808508754, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Maverick-17B-128E]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output1-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.018128167022950947, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider fireworks does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.0001929170684888959, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 40, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_basic[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.0063874589977785945, + "outcome": "passed" + }, + "call": { + "duration": 0.8047525839647278, + "outcome": "passed" + }, + "teardown": { + "duration": 0.00039245898369699717, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-gpt-4o]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output1-gpt-4o]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.01366533397231251, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider fireworks does not support model gpt-4o')" + }, + "teardown": { + "duration": 0.00028241705149412155, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-gpt-4o-mini]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output1-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.010844790958799422, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider fireworks does not support model gpt-4o-mini')" + }, + "teardown": { + "duration": 0.000258082989603281, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Scout-17B-16E]", + "lineno": 60, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_image[input_output0-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.00936354196164757, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 61, 'Skipped: Provider fireworks does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.00020533299539238214, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 60, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_image[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.008578249951824546, + "outcome": "passed" + }, + "call": { + "duration": 2.6288582499837503, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0006052498938515782, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Maverick-17B-128E]", + "lineno": 60, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_image[input_output0-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.02061279199551791, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 61, 'Skipped: Provider fireworks does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.00029320805333554745, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 60, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_image[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.00995812495239079, + "outcome": "passed" + }, + "call": { + "duration": 3.0904540000483394, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0003214169992133975, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-gpt-4o]", + "lineno": 60, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_image[input_output0-gpt-4o]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.0261635419446975, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 61, 'Skipped: Provider fireworks does not support model gpt-4o')" + }, + "teardown": { + "duration": 0.00032716698478907347, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-gpt-4o-mini]", + "lineno": 60, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_image[input_output0-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.027220541960559785, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 61, 'Skipped: Provider fireworks does not support model gpt-4o-mini')" + }, + "teardown": { + "duration": 0.0003192499279975891, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Scout-17B-16E]", + "lineno": 75, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_image[input_output0-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.010883458075113595, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 76, 'Skipped: Provider fireworks does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.0002687909873202443, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 75, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_image[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.0171177500160411, + "outcome": "passed" + }, + "call": { + "duration": 1.6752691670553759, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0004877089522778988, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Maverick-17B-128E]", + "lineno": 75, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_image[input_output0-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.011608208995312452, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 76, 'Skipped: Provider fireworks does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.00017137499526143074, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 75, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_image[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.009284624946303666, + "outcome": "passed" + }, + "call": { + "duration": 3.537356249988079, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0005068340105935931, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-gpt-4o]", + "lineno": 75, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_image[input_output0-gpt-4o]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.016660499968566, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 76, 'Skipped: Provider fireworks does not support model gpt-4o')" + }, + "teardown": { + "duration": 0.00029341597110033035, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-gpt-4o-mini]", + "lineno": 75, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_image[input_output0-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.01374066702555865, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 76, 'Skipped: Provider fireworks does not support model gpt-4o-mini')" + }, + "teardown": { + "duration": 0.0002625000197440386, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-3.3-8B-Instruct]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.013120374991558492, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider fireworks does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.00021954195108264685, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-3.3-70B-Instruct]", + "lineno": 95, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.015080374898388982, + "outcome": "passed" + }, + "call": { + "duration": 1.157175041968003, + "outcome": "passed" + }, + "teardown": { + "duration": 0.000495875021442771, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.013946042046882212, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider fireworks does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.0002954580122604966, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 95, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.011617792071774602, + "outcome": "passed" + }, + "call": { + "duration": 0.9537639999762177, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0004819999448955059, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.027436082949861884, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider fireworks does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.00030274991877377033, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 95, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.016110333963297307, + "outcome": "passed" + }, + "call": { + "duration": 0.8493227910948917, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0004883749643340707, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-gpt-4o]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-gpt-4o]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.017850833013653755, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider fireworks does not support model gpt-4o')" + }, + "teardown": { + "duration": 0.0003287500003352761, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-gpt-4o-mini]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.012523208046332002, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider fireworks does not support model gpt-4o-mini')" + }, + "teardown": { + "duration": 0.00023500004317611456, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-3.3-8B-Instruct]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.007516667013987899, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider fireworks does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.00018912507221102715, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-3.3-70B-Instruct]", + "lineno": 95, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.007337165996432304, + "outcome": "passed" + }, + "call": { + "duration": 3.124099582899362, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0006703329272568226, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.014259999967180192, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider fireworks does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.00030262500513345003, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 95, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.010863124975003302, + "outcome": "passed" + }, + "call": { + "duration": 1.3330956250429153, + "outcome": "passed" + }, + "teardown": { + "duration": 0.00018679199274629354, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.005797958001494408, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider fireworks does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.00017529097385704517, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 95, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.005647709011100233, + "outcome": "passed" + }, + "call": { + "duration": 3.2295467499643564, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0005654999986290932, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-gpt-4o]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-gpt-4o]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.007151791942305863, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider fireworks does not support model gpt-4o')" + }, + "teardown": { + "duration": 0.00015316694043576717, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-gpt-4o-mini]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.006435790914110839, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider fireworks does not support model gpt-4o-mini')" + }, + "teardown": { + "duration": 0.00015954102855175734, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-3.3-8B-Instruct]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.006164791993796825, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider fireworks does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.00014074996579438448, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-3.3-70B-Instruct]", + "lineno": 117, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.010064583038911223, + "outcome": "passed" + }, + "call": { + "duration": 1.1676458748988807, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0002513329964131117, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.011011417023837566, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider fireworks does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.00020608294289559126, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 117, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.011654542060568929, + "outcome": "passed" + }, + "call": { + "duration": 0.7950789160095155, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0002690000692382455, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.0066834589233621955, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider fireworks does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.00017270795069634914, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 117, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.011390416999347508, + "outcome": "passed" + }, + "call": { + "duration": 0.7844940840732306, + "outcome": "passed" + }, + "teardown": { + "duration": 0.000511458027176559, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-gpt-4o]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-gpt-4o]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.005813500029034913, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider fireworks does not support model gpt-4o')" + }, + "teardown": { + "duration": 0.00015495799016207457, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-gpt-4o-mini]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.0075639160349965096, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider fireworks does not support model gpt-4o-mini')" + }, + "teardown": { + "duration": 0.00014358304906636477, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-3.3-8B-Instruct]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.008526541059836745, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider fireworks does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.00015841599088162184, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-3.3-70B-Instruct]", + "lineno": 117, + "outcome": "failed", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.007805416011251509, + "outcome": "passed" + }, + "call": { + "duration": 13.25898533302825, + "outcome": "failed", + "crash": { + "path": "/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py", + "lineno": 196, + "message": "assert None is not None" + }, + "traceback": [ + { + "path": "tests/verifications/openai/test_chat_completion.py", + "lineno": 136, + "message": "" + }, + { + "path": "tests/verifications/openai/test_chat_completion.py", + "lineno": 196, + "message": "AssertionError" + } + ], + "longrepr": "openai_client = \ninput_output = {'input': {'messages': [{'content': 'You are a helpful math tutor. Guide the user through the solution step by step.',... ['steps', 'final_answer'], 'title': 'MathReasoning', ...}}, 'type': 'json_schema'}}, 'output': 'valid_math_reasoning'}\ncorrect_model_name = 'accounts/fireworks/models/llama-v3p1-70b-instruct'\n\n @pytest.mark.parametrize(\n \"model\",\n chat_completion_test_cases[\"test_chat_structured_output\"][\"test_params\"][\"model\"],\n )\n @pytest.mark.parametrize(\n \"input_output\",\n chat_completion_test_cases[\"test_chat_structured_output\"][\"test_params\"][\"input_output\"],\n )\n def test_chat_streaming_structured_output(openai_client, input_output, correct_model_name):\n response = openai_client.chat.completions.create(\n model=correct_model_name,\n messages=input_output[\"input\"][\"messages\"],\n response_format=input_output[\"input\"][\"response_format\"],\n stream=True,\n )\n maybe_json_content = \"\"\n for chunk in response:\n maybe_json_content += chunk.choices[0].delta.content or \"\"\n> validate_structured_output(maybe_json_content, input_output[\"output\"])\n\ntests/verifications/openai/test_chat_completion.py:136: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nmaybe_json_content = '{ \"final_answer\": \"}To solve the equation 8x + 7 = -23, we need to isolate the variable x. We can do this by followin...tassistantassistantassistantassistantassistantassistantassistantassistantassistantassistantassistantassistantassistant'\nschema_name = 'valid_math_reasoning'\n\n def validate_structured_output(maybe_json_content: str, schema_name: str) -> None:\n structured_output = get_structured_output(maybe_json_content, schema_name)\n> assert structured_output is not None\nE assert None is not None\n\ntests/verifications/openai/test_chat_completion.py:196: AssertionError" + }, + "teardown": { + "duration": 0.00022583396639674902, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.006412541959434748, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider fireworks does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.0001449589617550373, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 117, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.010353000019676983, + "outcome": "passed" + }, + "call": { + "duration": 4.559281209018081, + "outcome": "passed" + }, + "teardown": { + "duration": 0.00021179206669330597, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.011320417048409581, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider fireworks does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.0001623749267309904, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 117, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.005637791007757187, + "outcome": "passed" + }, + "call": { + "duration": 2.9282109580235556, + "outcome": "passed" + }, + "teardown": { + "duration": 0.00019149994477629662, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-gpt-4o]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-gpt-4o]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.021475916961207986, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider fireworks does not support model gpt-4o')" + }, + "teardown": { + "duration": 0.0002605828922241926, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-gpt-4o-mini]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.012046082993037999, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider fireworks does not support model gpt-4o-mini')" + }, + "teardown": { + "duration": 0.00016966694965958595, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-3.3-70B-Instruct]", + "lineno": 138, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_tool_calling[input_output0-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.00782629195600748, + "outcome": "passed" + }, + "call": { + "duration": 0.9290615000063553, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0004110001027584076, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Scout-17B-16E]", + "lineno": 138, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_tool_calling[input_output0-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.00842183397617191, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 139, 'Skipped: Provider fireworks does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.00023745803628116846, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 138, + "outcome": "failed", + "keywords": [ + "test_chat_non_streaming_tool_calling[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.010762874968349934, + "outcome": "passed" + }, + "call": { + "duration": 23.62101216695737, + "outcome": "failed", + "crash": { + "path": "/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py", + "lineno": 156, + "message": "TypeError: object of type 'NoneType' has no len()" + }, + "traceback": [ + { + "path": "tests/verifications/openai/test_chat_completion.py", + "lineno": 156, + "message": "TypeError" + } + ], + "longrepr": "openai_client = \ninput_output = {'input': {'messages': [{'content': 'You are a helpful assistant that can use tools to get information.', 'role': 'sys..., 'properties': {...}, 'required': [...], 'type': 'object'}}, 'type': 'function'}]}, 'output': 'get_weather_tool_call'}\ncorrect_model_name = 'accounts/fireworks/models/llama4-scout-instruct-basic'\n\n @pytest.mark.parametrize(\n \"model\",\n chat_completion_test_cases[\"test_tool_calling\"][\"test_params\"][\"model\"],\n )\n @pytest.mark.parametrize(\n \"input_output\",\n chat_completion_test_cases[\"test_tool_calling\"][\"test_params\"][\"input_output\"],\n )\n def test_chat_non_streaming_tool_calling(openai_client, input_output, correct_model_name):\n response = openai_client.chat.completions.create(\n model=correct_model_name,\n messages=input_output[\"input\"][\"messages\"],\n tools=input_output[\"input\"][\"tools\"],\n stream=False,\n )\n \n assert response.choices[0].message.role == \"assistant\"\n> assert len(response.choices[0].message.tool_calls) > 0\nE TypeError: object of type 'NoneType' has no len()\n\ntests/verifications/openai/test_chat_completion.py:156: TypeError" + }, + "teardown": { + "duration": 0.0004520840011537075, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Maverick-17B-128E]", + "lineno": 138, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_tool_calling[input_output0-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.00953104195650667, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 139, 'Skipped: Provider fireworks does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.00017912499606609344, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 138, + "outcome": "failed", + "keywords": [ + "test_chat_non_streaming_tool_calling[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.010302042006514966, + "outcome": "passed" + }, + "call": { + "duration": 5.55651158397086, + "outcome": "failed", + "crash": { + "path": "/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py", + "lineno": 156, + "message": "TypeError: object of type 'NoneType' has no len()" + }, + "traceback": [ + { + "path": "tests/verifications/openai/test_chat_completion.py", + "lineno": 156, + "message": "TypeError" + } + ], + "longrepr": "openai_client = \ninput_output = {'input': {'messages': [{'content': 'You are a helpful assistant that can use tools to get information.', 'role': 'sys..., 'properties': {...}, 'required': [...], 'type': 'object'}}, 'type': 'function'}]}, 'output': 'get_weather_tool_call'}\ncorrect_model_name = 'accounts/fireworks/models/llama4-maverick-instruct-basic'\n\n @pytest.mark.parametrize(\n \"model\",\n chat_completion_test_cases[\"test_tool_calling\"][\"test_params\"][\"model\"],\n )\n @pytest.mark.parametrize(\n \"input_output\",\n chat_completion_test_cases[\"test_tool_calling\"][\"test_params\"][\"input_output\"],\n )\n def test_chat_non_streaming_tool_calling(openai_client, input_output, correct_model_name):\n response = openai_client.chat.completions.create(\n model=correct_model_name,\n messages=input_output[\"input\"][\"messages\"],\n tools=input_output[\"input\"][\"tools\"],\n stream=False,\n )\n \n assert response.choices[0].message.role == \"assistant\"\n> assert len(response.choices[0].message.tool_calls) > 0\nE TypeError: object of type 'NoneType' has no len()\n\ntests/verifications/openai/test_chat_completion.py:156: TypeError" + }, + "teardown": { + "duration": 0.0003929579397663474, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-gpt-4o]", + "lineno": 138, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_tool_calling[input_output0-gpt-4o]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.01593891705852002, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 139, 'Skipped: Provider fireworks does not support model gpt-4o')" + }, + "teardown": { + "duration": 0.0003579579060897231, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-gpt-4o-mini]", + "lineno": 138, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_tool_calling[input_output0-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.01874550001230091, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 139, 'Skipped: Provider fireworks does not support model gpt-4o-mini')" + }, + "teardown": { + "duration": 0.00031995808240026236, + "outcome": "passed" + } + } + ] +} diff --git a/tests/verifications/test_results/openai_1744154522.json b/tests/verifications/test_results/openai_1744154522.json new file mode 100644 index 000000000..310f3500d --- /dev/null +++ b/tests/verifications/test_results/openai_1744154522.json @@ -0,0 +1,2672 @@ +{ + "created": 1744154576.251519, + "duration": 51.50739002227783, + "exitcode": 0, + "root": "/Users/erichuang/projects/llama-stack", + "environment": {}, + "summary": { + "skipped": 61, + "passed": 22, + "total": 83, + "collected": 83 + }, + "collectors": [ + { + "nodeid": "", + "outcome": "passed", + "result": [ + { + "nodeid": "tests/verifications/openai/test_chat_completion.py", + "type": "Module" + } + ] + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py", + "outcome": "passed", + "result": [ + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-gpt-4o]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-gpt-4o-mini]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-gpt-4o]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-gpt-4o-mini]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-gpt-4o]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-gpt-4o-mini]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-gpt-4o]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-gpt-4o-mini]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 60 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 60 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 60 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 60 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-gpt-4o]", + "type": "Function", + "lineno": 60 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-gpt-4o-mini]", + "type": "Function", + "lineno": 60 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 75 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 75 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 75 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 75 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-gpt-4o]", + "type": "Function", + "lineno": 75 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-gpt-4o-mini]", + "type": "Function", + "lineno": 75 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-gpt-4o]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-gpt-4o-mini]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-gpt-4o]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-gpt-4o-mini]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-gpt-4o]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-gpt-4o-mini]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-gpt-4o]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-gpt-4o-mini]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 138 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 138 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 138 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 138 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 138 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-gpt-4o]", + "type": "Function", + "lineno": 138 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-gpt-4o-mini]", + "type": "Function", + "lineno": 138 + } + ] + } + ], + "tests": [ + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-3.3-8B-Instruct]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.0531630830373615, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider openai does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.0001657919492572546, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-3.3-70B-Instruct]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.006063499953597784, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider openai does not support model Llama-3.3-70B-Instruct')" + }, + "teardown": { + "duration": 0.00014004099648445845, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Scout-17B-16E]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.005356832989491522, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider openai does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.00016508297994732857, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.006139832898043096, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider openai does not support model Llama-4-Scout-17B-16E-Instruct')" + }, + "teardown": { + "duration": 0.00014450005255639553, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Maverick-17B-128E]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.00542324990965426, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider openai does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.00014112505596131086, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.004965625004842877, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider openai does not support model Llama-4-Maverick-17B-128E-Instruct')" + }, + "teardown": { + "duration": 0.00013720791321247816, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-gpt-4o]", + "lineno": 25, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-gpt-4o]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.005054084002040327, + "outcome": "passed" + }, + "call": { + "duration": 0.6271341659594327, + "outcome": "passed" + }, + "teardown": { + "duration": 0.00043925002682954073, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-gpt-4o-mini]", + "lineno": 25, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.0159178749890998, + "outcome": "passed" + }, + "call": { + "duration": 0.44088316697161645, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0006467089988291264, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-3.3-8B-Instruct]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.016705541987903416, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider openai does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.0005769169656559825, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-3.3-70B-Instruct]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.012067249976098537, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider openai does not support model Llama-3.3-70B-Instruct')" + }, + "teardown": { + "duration": 0.00016683305148035288, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Scout-17B-16E]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.009295083000324667, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider openai does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.00017204193864017725, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.009534333017654717, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider openai does not support model Llama-4-Scout-17B-16E-Instruct')" + }, + "teardown": { + "duration": 0.00020175008103251457, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Maverick-17B-128E]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.006628665956668556, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider openai does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.0003687090938910842, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.0061322919791564345, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider openai does not support model Llama-4-Maverick-17B-128E-Instruct')" + }, + "teardown": { + "duration": 0.0003664169926196337, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-gpt-4o]", + "lineno": 25, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-gpt-4o]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.00623433303553611, + "outcome": "passed" + }, + "call": { + "duration": 0.7898445830214769, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0006602079374715686, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-gpt-4o-mini]", + "lineno": 25, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.014758958015590906, + "outcome": "passed" + }, + "call": { + "duration": 1.1555478329537436, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0011781250359490514, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-3.3-8B-Instruct]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output0-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.03454475000035018, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider openai does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.000967124942690134, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-3.3-70B-Instruct]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output0-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.025206666090525687, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider openai does not support model Llama-3.3-70B-Instruct')" + }, + "teardown": { + "duration": 0.000189624959602952, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Scout-17B-16E]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output0-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.014331333106383681, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider openai does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.00023133307695388794, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.009339665994048119, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider openai does not support model Llama-4-Scout-17B-16E-Instruct')" + }, + "teardown": { + "duration": 0.00020329200197011232, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Maverick-17B-128E]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output0-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.010387042071670294, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider openai does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.00018254201859235764, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.012297999928705394, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider openai does not support model Llama-4-Maverick-17B-128E-Instruct')" + }, + "teardown": { + "duration": 0.00018662505317479372, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-gpt-4o]", + "lineno": 40, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_basic[input_output0-gpt-4o]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.006984042003750801, + "outcome": "passed" + }, + "call": { + "duration": 0.32529433304443955, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0033042499562725425, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-gpt-4o-mini]", + "lineno": 40, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_basic[input_output0-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.01832079200539738, + "outcome": "passed" + }, + "call": { + "duration": 0.48440287495031953, + "outcome": "passed" + }, + "teardown": { + "duration": 0.00047233293298631907, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-3.3-8B-Instruct]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output1-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.02893691696226597, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider openai does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.0001747499918565154, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-3.3-70B-Instruct]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output1-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.006553041050210595, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider openai does not support model Llama-3.3-70B-Instruct')" + }, + "teardown": { + "duration": 0.00016829196829348803, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Scout-17B-16E]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output1-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.013746666954830289, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider openai does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.00019237503875046968, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.007175332983024418, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider openai does not support model Llama-4-Scout-17B-16E-Instruct')" + }, + "teardown": { + "duration": 0.0001873329747468233, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Maverick-17B-128E]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output1-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.006127291941083968, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider openai does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.00019004102796316147, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.006421791040338576, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider openai does not support model Llama-4-Maverick-17B-128E-Instruct')" + }, + "teardown": { + "duration": 0.0001611249754205346, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-gpt-4o]", + "lineno": 40, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_basic[input_output1-gpt-4o]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.009806249989196658, + "outcome": "passed" + }, + "call": { + "duration": 0.9556747920578346, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0004937920020893216, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-gpt-4o-mini]", + "lineno": 40, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_basic[input_output1-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.03146500000730157, + "outcome": "passed" + }, + "call": { + "duration": 1.082494750036858, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0006242080125957727, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Scout-17B-16E]", + "lineno": 60, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_image[input_output0-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.021534667001105845, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 61, 'Skipped: Provider openai does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.0003469999646767974, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 60, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_image[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.025929750059731305, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 61, 'Skipped: Provider openai does not support model Llama-4-Scout-17B-16E-Instruct')" + }, + "teardown": { + "duration": 0.0008774169255048037, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Maverick-17B-128E]", + "lineno": 60, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_image[input_output0-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.012507125036790967, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 61, 'Skipped: Provider openai does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.00022008304949849844, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 60, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_image[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.008156375028192997, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 61, 'Skipped: Provider openai does not support model Llama-4-Maverick-17B-128E-Instruct')" + }, + "teardown": { + "duration": 0.0002079169498756528, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-gpt-4o]", + "lineno": 60, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_image[input_output0-gpt-4o]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.012587749981321394, + "outcome": "passed" + }, + "call": { + "duration": 2.7379885419504717, + "outcome": "passed" + }, + "teardown": { + "duration": 0.00044579198583960533, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-gpt-4o-mini]", + "lineno": 60, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_image[input_output0-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.017111250082962215, + "outcome": "passed" + }, + "call": { + "duration": 2.599374584038742, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0009177909232676029, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Scout-17B-16E]", + "lineno": 75, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_image[input_output0-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.02198700001463294, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 76, 'Skipped: Provider openai does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.00042749999556690454, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 75, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_image[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.015032917028293014, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 76, 'Skipped: Provider openai does not support model Llama-4-Scout-17B-16E-Instruct')" + }, + "teardown": { + "duration": 0.00041016703471541405, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Maverick-17B-128E]", + "lineno": 75, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_image[input_output0-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.013976250076666474, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 76, 'Skipped: Provider openai does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.00027600000612437725, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 75, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_image[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.00799729092977941, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 76, 'Skipped: Provider openai does not support model Llama-4-Maverick-17B-128E-Instruct')" + }, + "teardown": { + "duration": 0.00020320899784564972, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-gpt-4o]", + "lineno": 75, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_image[input_output0-gpt-4o]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.010483540943823755, + "outcome": "passed" + }, + "call": { + "duration": 4.249965250026435, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0008596250554546714, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-gpt-4o-mini]", + "lineno": 75, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_image[input_output0-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.018141582957468927, + "outcome": "passed" + }, + "call": { + "duration": 2.297856790944934, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0005075830267742276, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-3.3-8B-Instruct]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.017144332989118993, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider openai does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.0006829580524936318, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-3.3-70B-Instruct]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.009827250032685697, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider openai does not support model Llama-3.3-70B-Instruct')" + }, + "teardown": { + "duration": 0.00024204188957810402, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.006737958989106119, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider openai does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.00022729102056473494, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.006030917051248252, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider openai does not support model Llama-4-Scout-17B-16E-Instruct')" + }, + "teardown": { + "duration": 0.00022229203023016453, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.009183833957649767, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider openai does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.00022629194427281618, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.007097500027157366, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider openai does not support model Llama-4-Maverick-17B-128E-Instruct')" + }, + "teardown": { + "duration": 0.00826825003605336, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-gpt-4o]", + "lineno": 95, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-gpt-4o]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.006604874972254038, + "outcome": "passed" + }, + "call": { + "duration": 1.4057738750707358, + "outcome": "passed" + }, + "teardown": { + "duration": 0.000506040989421308, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-gpt-4o-mini]", + "lineno": 95, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.015966624952852726, + "outcome": "passed" + }, + "call": { + "duration": 0.540478374925442, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0009536249563097954, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-3.3-8B-Instruct]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.020631707971915603, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider openai does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.0004928340204060078, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-3.3-70B-Instruct]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.016745459055528045, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider openai does not support model Llama-3.3-70B-Instruct')" + }, + "teardown": { + "duration": 0.0003412909572944045, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.012252667103894055, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider openai does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.00028650008607655764, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.01128904102370143, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider openai does not support model Llama-4-Scout-17B-16E-Instruct')" + }, + "teardown": { + "duration": 0.00027041707653552294, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.009191332967020571, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider openai does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.0002074999501928687, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.007687666919082403, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider openai does not support model Llama-4-Maverick-17B-128E-Instruct')" + }, + "teardown": { + "duration": 0.0002027079463005066, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-gpt-4o]", + "lineno": 95, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-gpt-4o]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.007542708073742688, + "outcome": "passed" + }, + "call": { + "duration": 4.244797708000988, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0012778330128639936, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-gpt-4o-mini]", + "lineno": 95, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.026919999974779785, + "outcome": "passed" + }, + "call": { + "duration": 9.006108874920756, + "outcome": "passed" + }, + "teardown": { + "duration": 0.00046324997674673796, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-3.3-8B-Instruct]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.01554666692391038, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider openai does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.0004023330984637141, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-3.3-70B-Instruct]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.007354958914220333, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider openai does not support model Llama-3.3-70B-Instruct')" + }, + "teardown": { + "duration": 0.0002900830004364252, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.017274250043556094, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider openai does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.0002668329980224371, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.006813667016103864, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider openai does not support model Llama-4-Scout-17B-16E-Instruct')" + }, + "teardown": { + "duration": 0.00024500000290572643, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.007385291974060237, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider openai does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.00017024995759129524, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.00857366609852761, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider openai does not support model Llama-4-Maverick-17B-128E-Instruct')" + }, + "teardown": { + "duration": 0.00016850000247359276, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-gpt-4o]", + "lineno": 117, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-gpt-4o]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.005570041947066784, + "outcome": "passed" + }, + "call": { + "duration": 0.8564215000951663, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0004029169213026762, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-gpt-4o-mini]", + "lineno": 117, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.00786762498319149, + "outcome": "passed" + }, + "call": { + "duration": 0.6419672920601442, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0005102079594507813, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-3.3-8B-Instruct]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.017147499951533973, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider openai does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.00032350001856684685, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-3.3-70B-Instruct]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.01194737502373755, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider openai does not support model Llama-3.3-70B-Instruct')" + }, + "teardown": { + "duration": 0.0005004579434171319, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.010250666993670166, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider openai does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.00022554199676960707, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.007847042055800557, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider openai does not support model Llama-4-Scout-17B-16E-Instruct')" + }, + "teardown": { + "duration": 0.000283458037301898, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.008078000042587519, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider openai does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.0001794169656932354, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.007204750087112188, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider openai does not support model Llama-4-Maverick-17B-128E-Instruct')" + }, + "teardown": { + "duration": 0.00017725001089274883, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-gpt-4o]", + "lineno": 117, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-gpt-4o]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.006797667010687292, + "outcome": "passed" + }, + "call": { + "duration": 5.411579457926564, + "outcome": "passed" + }, + "teardown": { + "duration": 0.001134666963480413, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-gpt-4o-mini]", + "lineno": 117, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.025059624924324453, + "outcome": "passed" + }, + "call": { + "duration": 9.112342999898829, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0009202499641105533, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-3.3-70B-Instruct]", + "lineno": 138, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_tool_calling[input_output0-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.024287916952744126, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 139, 'Skipped: Provider openai does not support model Llama-3.3-70B-Instruct')" + }, + "teardown": { + "duration": 0.00015587499365210533, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Scout-17B-16E]", + "lineno": 138, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_tool_calling[input_output0-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.006531457998789847, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 139, 'Skipped: Provider openai does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.00014670798555016518, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 138, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_tool_calling[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.006190375075675547, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 139, 'Skipped: Provider openai does not support model Llama-4-Scout-17B-16E-Instruct')" + }, + "teardown": { + "duration": 0.0001603750279173255, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Maverick-17B-128E]", + "lineno": 138, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_tool_calling[input_output0-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.005670750048011541, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 139, 'Skipped: Provider openai does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.0001479999627918005, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 138, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_tool_calling[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.005662833107635379, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 139, 'Skipped: Provider openai does not support model Llama-4-Maverick-17B-128E-Instruct')" + }, + "teardown": { + "duration": 0.0001480829669162631, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-gpt-4o]", + "lineno": 138, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_tool_calling[input_output0-gpt-4o]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.00573637499473989, + "outcome": "passed" + }, + "call": { + "duration": 0.6269576249178499, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0010142088867723942, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-gpt-4o-mini]", + "lineno": 138, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_tool_calling[input_output0-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.01623620803002268, + "outcome": "passed" + }, + "call": { + "duration": 0.7144521250156686, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0011040839599445462, + "outcome": "passed" + } + } + ] +} diff --git a/tests/verifications/test_results/together_1744154399.json b/tests/verifications/test_results/together_1744154399.json new file mode 100644 index 000000000..ae801e83b --- /dev/null +++ b/tests/verifications/test_results/together_1744154399.json @@ -0,0 +1,2830 @@ +{ + "created": 1744154470.9868789, + "duration": 59.6187219619751, + "exitcode": 1, + "root": "/Users/erichuang/projects/llama-stack", + "environment": {}, + "summary": { + "skipped": 52, + "passed": 21, + "failed": 10, + "total": 83, + "collected": 83 + }, + "collectors": [ + { + "nodeid": "", + "outcome": "passed", + "result": [ + { + "nodeid": "tests/verifications/openai/test_chat_completion.py", + "type": "Module" + } + ] + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py", + "outcome": "passed", + "result": [ + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-gpt-4o]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-gpt-4o-mini]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-gpt-4o]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-gpt-4o-mini]", + "type": "Function", + "lineno": 25 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-gpt-4o]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-gpt-4o-mini]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-gpt-4o]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-gpt-4o-mini]", + "type": "Function", + "lineno": 40 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 60 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 60 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 60 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 60 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-gpt-4o]", + "type": "Function", + "lineno": 60 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-gpt-4o-mini]", + "type": "Function", + "lineno": 60 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 75 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 75 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 75 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 75 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-gpt-4o]", + "type": "Function", + "lineno": 75 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-gpt-4o-mini]", + "type": "Function", + "lineno": 75 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-gpt-4o]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-gpt-4o-mini]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-gpt-4o]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-gpt-4o-mini]", + "type": "Function", + "lineno": 95 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-gpt-4o]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-gpt-4o-mini]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-3.3-8B-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-gpt-4o]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-gpt-4o-mini]", + "type": "Function", + "lineno": 117 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-3.3-70B-Instruct]", + "type": "Function", + "lineno": 138 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Scout-17B-16E]", + "type": "Function", + "lineno": 138 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "type": "Function", + "lineno": 138 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Maverick-17B-128E]", + "type": "Function", + "lineno": 138 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "type": "Function", + "lineno": 138 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-gpt-4o]", + "type": "Function", + "lineno": 138 + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-gpt-4o-mini]", + "type": "Function", + "lineno": 138 + } + ] + } + ], + "tests": [ + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-3.3-8B-Instruct]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.39231995795853436, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider together does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.0002014160854741931, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-3.3-70B-Instruct]", + "lineno": 25, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.0071710830088704824, + "outcome": "passed" + }, + "call": { + "duration": 0.7968309168936685, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0004362498875707388, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Scout-17B-16E]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.012780916062183678, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider together does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.00029158301185816526, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 25, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.013563874992541969, + "outcome": "passed" + }, + "call": { + "duration": 0.5071627920260653, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0005456249928101897, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Maverick-17B-128E]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.020708917058072984, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider together does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.00030325003899633884, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 25, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.014170082984492183, + "outcome": "passed" + }, + "call": { + "duration": 1.2383921250002459, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0009597090538591146, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-gpt-4o]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-gpt-4o]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.013402250013314188, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider together does not support model gpt-4o')" + }, + "teardown": { + "duration": 0.00028245802968740463, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output0-gpt-4o-mini]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output0-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.008693707990460098, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider together does not support model gpt-4o-mini')" + }, + "teardown": { + "duration": 0.00016249995678663254, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-3.3-8B-Instruct]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.005904874997213483, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider together does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.0001960420049726963, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-3.3-70B-Instruct]", + "lineno": 25, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.006532749976031482, + "outcome": "passed" + }, + "call": { + "duration": 0.5410778749501333, + "outcome": "passed" + }, + "teardown": { + "duration": 0.00019516597967594862, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Scout-17B-16E]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.009374375105835497, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider together does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.00015524995978921652, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 25, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.007205875008367002, + "outcome": "passed" + }, + "call": { + "duration": 0.42584729101508856, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0009506250498816371, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Maverick-17B-128E]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.029625958995893598, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider together does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.0001860830234363675, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 25, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.023576707928441465, + "outcome": "passed" + }, + "call": { + "duration": 1.2249365829629824, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0004278330598026514, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-gpt-4o]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-gpt-4o]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.014816291979514062, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider together does not support model gpt-4o')" + }, + "teardown": { + "duration": 0.00029558304231613874, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_basic[input_output1-gpt-4o-mini]", + "lineno": 25, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_basic[input_output1-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.012769333901815116, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 26, 'Skipped: Provider together does not support model gpt-4o-mini')" + }, + "teardown": { + "duration": 0.00024329195730388165, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-3.3-8B-Instruct]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output0-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.009145625052042305, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider together does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.00021195888984948397, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-3.3-70B-Instruct]", + "lineno": 40, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_basic[input_output0-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.0133140409598127, + "outcome": "passed" + }, + "call": { + "duration": 0.7228892090497538, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0004301250446587801, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Scout-17B-16E]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output0-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.013998750015161932, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider together does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.0002961249556392431, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 40, + "outcome": "failed", + "keywords": [ + "test_chat_streaming_basic[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.012570249964483082, + "outcome": "passed" + }, + "call": { + "duration": 0.7193170419195667, + "outcome": "failed", + "crash": { + "path": "/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py", + "lineno": 54, + "message": "IndexError: list index out of range" + }, + "traceback": [ + { + "path": "tests/verifications/openai/test_chat_completion.py", + "lineno": 54, + "message": "IndexError" + } + ], + "longrepr": "openai_client = \ninput_output = {'input': {'messages': [{'content': 'Which planet do humans live on?', 'role': 'user'}]}, 'output': 'Earth'}\ncorrect_model_name = 'meta-llama/Llama-4-Scout-17B-16E-Instruct'\n\n @pytest.mark.parametrize(\"model\", chat_completion_test_cases[\"test_chat_basic\"][\"test_params\"][\"model\"])\n @pytest.mark.parametrize(\n \"input_output\",\n chat_completion_test_cases[\"test_chat_basic\"][\"test_params\"][\"input_output\"],\n )\n def test_chat_streaming_basic(openai_client, input_output, correct_model_name):\n response = openai_client.chat.completions.create(\n model=correct_model_name,\n messages=input_output[\"input\"][\"messages\"],\n stream=True,\n )\n content = \"\"\n for chunk in response:\n> content += chunk.choices[0].delta.content or \"\"\nE IndexError: list index out of range\n\ntests/verifications/openai/test_chat_completion.py:54: IndexError" + }, + "teardown": { + "duration": 0.00022504094522446394, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Maverick-17B-128E]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output0-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.006660082959569991, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider together does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.0001445829402655363, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 40, + "outcome": "failed", + "keywords": [ + "test_chat_streaming_basic[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.021228999947197735, + "outcome": "passed" + }, + "call": { + "duration": 1.5670281670754775, + "outcome": "failed", + "crash": { + "path": "/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py", + "lineno": 54, + "message": "IndexError: list index out of range" + }, + "traceback": [ + { + "path": "tests/verifications/openai/test_chat_completion.py", + "lineno": 54, + "message": "IndexError" + } + ], + "longrepr": "openai_client = \ninput_output = {'input': {'messages': [{'content': 'Which planet do humans live on?', 'role': 'user'}]}, 'output': 'Earth'}\ncorrect_model_name = 'meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8'\n\n @pytest.mark.parametrize(\"model\", chat_completion_test_cases[\"test_chat_basic\"][\"test_params\"][\"model\"])\n @pytest.mark.parametrize(\n \"input_output\",\n chat_completion_test_cases[\"test_chat_basic\"][\"test_params\"][\"input_output\"],\n )\n def test_chat_streaming_basic(openai_client, input_output, correct_model_name):\n response = openai_client.chat.completions.create(\n model=correct_model_name,\n messages=input_output[\"input\"][\"messages\"],\n stream=True,\n )\n content = \"\"\n for chunk in response:\n> content += chunk.choices[0].delta.content or \"\"\nE IndexError: list index out of range\n\ntests/verifications/openai/test_chat_completion.py:54: IndexError" + }, + "teardown": { + "duration": 0.0004656669916585088, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-gpt-4o]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output0-gpt-4o]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.009595917072147131, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider together does not support model gpt-4o')" + }, + "teardown": { + "duration": 0.00025625003036111593, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output0-gpt-4o-mini]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output0-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.009242708911187947, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider together does not support model gpt-4o-mini')" + }, + "teardown": { + "duration": 0.0002484159776940942, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-3.3-8B-Instruct]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output1-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.00905474997125566, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider together does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.00023312494158744812, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-3.3-70B-Instruct]", + "lineno": 40, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_basic[input_output1-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.007183165987953544, + "outcome": "passed" + }, + "call": { + "duration": 1.0667660840554163, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0005163750611245632, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Scout-17B-16E]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output1-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.05233616603072733, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider together does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.0003471659729257226, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 40, + "outcome": "failed", + "keywords": [ + "test_chat_streaming_basic[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.015932541922666132, + "outcome": "passed" + }, + "call": { + "duration": 0.41540695796720684, + "outcome": "failed", + "crash": { + "path": "/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py", + "lineno": 54, + "message": "IndexError: list index out of range" + }, + "traceback": [ + { + "path": "tests/verifications/openai/test_chat_completion.py", + "lineno": 54, + "message": "IndexError" + } + ], + "longrepr": "openai_client = \ninput_output = {'input': {'messages': [{'content': 'Which planet has rings around it with a name starting with letter S?', 'role': 'user'}]}, 'output': 'Saturn'}\ncorrect_model_name = 'meta-llama/Llama-4-Scout-17B-16E-Instruct'\n\n @pytest.mark.parametrize(\"model\", chat_completion_test_cases[\"test_chat_basic\"][\"test_params\"][\"model\"])\n @pytest.mark.parametrize(\n \"input_output\",\n chat_completion_test_cases[\"test_chat_basic\"][\"test_params\"][\"input_output\"],\n )\n def test_chat_streaming_basic(openai_client, input_output, correct_model_name):\n response = openai_client.chat.completions.create(\n model=correct_model_name,\n messages=input_output[\"input\"][\"messages\"],\n stream=True,\n )\n content = \"\"\n for chunk in response:\n> content += chunk.choices[0].delta.content or \"\"\nE IndexError: list index out of range\n\ntests/verifications/openai/test_chat_completion.py:54: IndexError" + }, + "teardown": { + "duration": 0.0002845840062946081, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Maverick-17B-128E]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output1-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.007243875064887106, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider together does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.00016258296091109514, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 40, + "outcome": "failed", + "keywords": [ + "test_chat_streaming_basic[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.009275624994188547, + "outcome": "passed" + }, + "call": { + "duration": 1.43309554096777, + "outcome": "failed", + "crash": { + "path": "/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py", + "lineno": 54, + "message": "IndexError: list index out of range" + }, + "traceback": [ + { + "path": "tests/verifications/openai/test_chat_completion.py", + "lineno": 54, + "message": "IndexError" + } + ], + "longrepr": "openai_client = \ninput_output = {'input': {'messages': [{'content': 'Which planet has rings around it with a name starting with letter S?', 'role': 'user'}]}, 'output': 'Saturn'}\ncorrect_model_name = 'meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8'\n\n @pytest.mark.parametrize(\"model\", chat_completion_test_cases[\"test_chat_basic\"][\"test_params\"][\"model\"])\n @pytest.mark.parametrize(\n \"input_output\",\n chat_completion_test_cases[\"test_chat_basic\"][\"test_params\"][\"input_output\"],\n )\n def test_chat_streaming_basic(openai_client, input_output, correct_model_name):\n response = openai_client.chat.completions.create(\n model=correct_model_name,\n messages=input_output[\"input\"][\"messages\"],\n stream=True,\n )\n content = \"\"\n for chunk in response:\n> content += chunk.choices[0].delta.content or \"\"\nE IndexError: list index out of range\n\ntests/verifications/openai/test_chat_completion.py:54: IndexError" + }, + "teardown": { + "duration": 0.0003690000157803297, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-gpt-4o]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output1-gpt-4o]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.011570582981221378, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider together does not support model gpt-4o')" + }, + "teardown": { + "duration": 0.00024937500711530447, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_basic[input_output1-gpt-4o-mini]", + "lineno": 40, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_basic[input_output1-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.010756584000773728, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 41, 'Skipped: Provider together does not support model gpt-4o-mini')" + }, + "teardown": { + "duration": 0.00026183295994997025, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Scout-17B-16E]", + "lineno": 60, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_image[input_output0-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.008863041992299259, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 61, 'Skipped: Provider together does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.00023283297196030617, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 60, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_image[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.007975792046636343, + "outcome": "passed" + }, + "call": { + "duration": 2.1585817909799516, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0005107080796733499, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Maverick-17B-128E]", + "lineno": 60, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_image[input_output0-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.05228079203516245, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 61, 'Skipped: Provider together does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.0017226670170202851, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 60, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_image[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.009964749915525317, + "outcome": "passed" + }, + "call": { + "duration": 4.6593364590080455, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0009852920193225145, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-gpt-4o]", + "lineno": 60, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_image[input_output0-gpt-4o]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.023214041953906417, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 61, 'Skipped: Provider together does not support model gpt-4o')" + }, + "teardown": { + "duration": 0.0003567079547792673, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_image[input_output0-gpt-4o-mini]", + "lineno": 60, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_image[input_output0-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.01705008395947516, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 61, 'Skipped: Provider together does not support model gpt-4o-mini')" + }, + "teardown": { + "duration": 0.0003085409989580512, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Scout-17B-16E]", + "lineno": 75, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_image[input_output0-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.014711958006955683, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 76, 'Skipped: Provider together does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.0003121249610558152, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 75, + "outcome": "failed", + "keywords": [ + "test_chat_streaming_image[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.01843333407305181, + "outcome": "passed" + }, + "call": { + "duration": 2.8683876669965684, + "outcome": "failed", + "crash": { + "path": "/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py", + "lineno": 89, + "message": "IndexError: list index out of range" + }, + "traceback": [ + { + "path": "tests/verifications/openai/test_chat_completion.py", + "lineno": 89, + "message": "IndexError" + } + ], + "longrepr": "openai_client = \ninput_output = {'input': {'messages': [{'content': [{'text': 'What is in this image?', 'type': 'text'}, {'image_url': {...}, 'type': 'image_url'}], 'role': 'user'}]}, 'output': 'llama'}\ncorrect_model_name = 'meta-llama/Llama-4-Scout-17B-16E-Instruct'\n\n @pytest.mark.parametrize(\"model\", chat_completion_test_cases[\"test_chat_image\"][\"test_params\"][\"model\"])\n @pytest.mark.parametrize(\n \"input_output\",\n chat_completion_test_cases[\"test_chat_image\"][\"test_params\"][\"input_output\"],\n )\n def test_chat_streaming_image(openai_client, input_output, correct_model_name):\n response = openai_client.chat.completions.create(\n model=correct_model_name,\n messages=input_output[\"input\"][\"messages\"],\n stream=True,\n )\n content = \"\"\n for chunk in response:\n> content += chunk.choices[0].delta.content or \"\"\nE IndexError: list index out of range\n\ntests/verifications/openai/test_chat_completion.py:89: IndexError" + }, + "teardown": { + "duration": 0.00028662499971687794, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Maverick-17B-128E]", + "lineno": 75, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_image[input_output0-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.00653208396397531, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 76, 'Skipped: Provider together does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.00021291698794811964, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 75, + "outcome": "failed", + "keywords": [ + "test_chat_streaming_image[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.006028458010405302, + "outcome": "passed" + }, + "call": { + "duration": 4.981105040991679, + "outcome": "failed", + "crash": { + "path": "/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py", + "lineno": 89, + "message": "IndexError: list index out of range" + }, + "traceback": [ + { + "path": "tests/verifications/openai/test_chat_completion.py", + "lineno": 89, + "message": "IndexError" + } + ], + "longrepr": "openai_client = \ninput_output = {'input': {'messages': [{'content': [{'text': 'What is in this image?', 'type': 'text'}, {'image_url': {...}, 'type': 'image_url'}], 'role': 'user'}]}, 'output': 'llama'}\ncorrect_model_name = 'meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8'\n\n @pytest.mark.parametrize(\"model\", chat_completion_test_cases[\"test_chat_image\"][\"test_params\"][\"model\"])\n @pytest.mark.parametrize(\n \"input_output\",\n chat_completion_test_cases[\"test_chat_image\"][\"test_params\"][\"input_output\"],\n )\n def test_chat_streaming_image(openai_client, input_output, correct_model_name):\n response = openai_client.chat.completions.create(\n model=correct_model_name,\n messages=input_output[\"input\"][\"messages\"],\n stream=True,\n )\n content = \"\"\n for chunk in response:\n> content += chunk.choices[0].delta.content or \"\"\nE IndexError: list index out of range\n\ntests/verifications/openai/test_chat_completion.py:89: IndexError" + }, + "teardown": { + "duration": 0.0010110830189660192, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-gpt-4o]", + "lineno": 75, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_image[input_output0-gpt-4o]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.01591233303770423, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 76, 'Skipped: Provider together does not support model gpt-4o')" + }, + "teardown": { + "duration": 0.0003783750580623746, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_image[input_output0-gpt-4o-mini]", + "lineno": 75, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_image[input_output0-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.010691000032238662, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 76, 'Skipped: Provider together does not support model gpt-4o-mini')" + }, + "teardown": { + "duration": 0.00027445796877145767, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-3.3-8B-Instruct]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.01258529198821634, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider together does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.0002044580178335309, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-3.3-70B-Instruct]", + "lineno": 95, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.010904791066423059, + "outcome": "passed" + }, + "call": { + "duration": 0.8311828339938074, + "outcome": "passed" + }, + "teardown": { + "duration": 0.00048687495291233063, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.029216791968792677, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider together does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.0002269580727443099, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 95, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.013182583032175899, + "outcome": "passed" + }, + "call": { + "duration": 1.7446029160637408, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0008087089518085122, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.02009516698308289, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider together does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.000320291961543262, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 95, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.015216833096928895, + "outcome": "passed" + }, + "call": { + "duration": 0.8049291669158265, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0005109170451760292, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-gpt-4o]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-gpt-4o]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.0171551660168916, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider together does not support model gpt-4o')" + }, + "teardown": { + "duration": 0.0005707499803975224, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output0-gpt-4o-mini]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output0-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.01131124992389232, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider together does not support model gpt-4o-mini')" + }, + "teardown": { + "duration": 0.0003044159384444356, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-3.3-8B-Instruct]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.0054290409898385406, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider together does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.00014645792543888092, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-3.3-70B-Instruct]", + "lineno": 95, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.011368000064976513, + "outcome": "passed" + }, + "call": { + "duration": 4.363120499998331, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0003998749889433384, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.04945958300959319, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider together does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.0002401659730821848, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 95, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.011090958025306463, + "outcome": "passed" + }, + "call": { + "duration": 4.699277375009842, + "outcome": "passed" + }, + "teardown": { + "duration": 0.000689250067807734, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.020744459005072713, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider together does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.0001836250303313136, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 95, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.005926624988205731, + "outcome": "passed" + }, + "call": { + "duration": 2.7814464160474017, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0009554170537739992, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-gpt-4o]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-gpt-4o]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.03027112502604723, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider together does not support model gpt-4o')" + }, + "teardown": { + "duration": 0.0003245410043746233, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_structured_output[input_output1-gpt-4o-mini]", + "lineno": 95, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_structured_output[input_output1-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.009138708002865314, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 96, 'Skipped: Provider together does not support model gpt-4o-mini')" + }, + "teardown": { + "duration": 0.0001919999485835433, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-3.3-8B-Instruct]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.0064505410846322775, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider together does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.00015720794908702374, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-3.3-70B-Instruct]", + "lineno": 117, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.00582624995149672, + "outcome": "passed" + }, + "call": { + "duration": 0.8302567919017747, + "outcome": "passed" + }, + "teardown": { + "duration": 0.00020354206208139658, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.014151416951790452, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider together does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.00034970801789313555, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 117, + "outcome": "failed", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.012150791939347982, + "outcome": "passed" + }, + "call": { + "duration": 0.7078855830477551, + "outcome": "failed", + "crash": { + "path": "/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py", + "lineno": 135, + "message": "IndexError: list index out of range" + }, + "traceback": [ + { + "path": "tests/verifications/openai/test_chat_completion.py", + "lineno": 135, + "message": "IndexError" + } + ], + "longrepr": "openai_client = \ninput_output = {'input': {'messages': [{'content': 'Extract the event information.', 'role': 'system'}, {'content': 'Alice and Bob ar...articipants'], 'title': 'CalendarEvent', 'type': 'object'}}, 'type': 'json_schema'}}, 'output': 'valid_calendar_event'}\ncorrect_model_name = 'meta-llama/Llama-4-Scout-17B-16E-Instruct'\n\n @pytest.mark.parametrize(\n \"model\",\n chat_completion_test_cases[\"test_chat_structured_output\"][\"test_params\"][\"model\"],\n )\n @pytest.mark.parametrize(\n \"input_output\",\n chat_completion_test_cases[\"test_chat_structured_output\"][\"test_params\"][\"input_output\"],\n )\n def test_chat_streaming_structured_output(openai_client, input_output, correct_model_name):\n response = openai_client.chat.completions.create(\n model=correct_model_name,\n messages=input_output[\"input\"][\"messages\"],\n response_format=input_output[\"input\"][\"response_format\"],\n stream=True,\n )\n maybe_json_content = \"\"\n for chunk in response:\n> maybe_json_content += chunk.choices[0].delta.content or \"\"\nE IndexError: list index out of range\n\ntests/verifications/openai/test_chat_completion.py:135: IndexError" + }, + "teardown": { + "duration": 0.0008542909054085612, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.022667833953164518, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider together does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.0006820419803261757, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 117, + "outcome": "failed", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.01285991701297462, + "outcome": "passed" + }, + "call": { + "duration": 0.6888671671040356, + "outcome": "failed", + "crash": { + "path": "/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py", + "lineno": 135, + "message": "IndexError: list index out of range" + }, + "traceback": [ + { + "path": "tests/verifications/openai/test_chat_completion.py", + "lineno": 135, + "message": "IndexError" + } + ], + "longrepr": "openai_client = \ninput_output = {'input': {'messages': [{'content': 'Extract the event information.', 'role': 'system'}, {'content': 'Alice and Bob ar...articipants'], 'title': 'CalendarEvent', 'type': 'object'}}, 'type': 'json_schema'}}, 'output': 'valid_calendar_event'}\ncorrect_model_name = 'meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8'\n\n @pytest.mark.parametrize(\n \"model\",\n chat_completion_test_cases[\"test_chat_structured_output\"][\"test_params\"][\"model\"],\n )\n @pytest.mark.parametrize(\n \"input_output\",\n chat_completion_test_cases[\"test_chat_structured_output\"][\"test_params\"][\"input_output\"],\n )\n def test_chat_streaming_structured_output(openai_client, input_output, correct_model_name):\n response = openai_client.chat.completions.create(\n model=correct_model_name,\n messages=input_output[\"input\"][\"messages\"],\n response_format=input_output[\"input\"][\"response_format\"],\n stream=True,\n )\n maybe_json_content = \"\"\n for chunk in response:\n> maybe_json_content += chunk.choices[0].delta.content or \"\"\nE IndexError: list index out of range\n\ntests/verifications/openai/test_chat_completion.py:135: IndexError" + }, + "teardown": { + "duration": 0.0007953330641612411, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-gpt-4o]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-gpt-4o]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.015029000001959503, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider together does not support model gpt-4o')" + }, + "teardown": { + "duration": 0.00015666603576391935, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output0-gpt-4o-mini]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output0-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.00622316705994308, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider together does not support model gpt-4o-mini')" + }, + "teardown": { + "duration": 0.0001533749746158719, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-3.3-8B-Instruct]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-Llama-3.3-8B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-8B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.005598834017291665, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider together does not support model Llama-3.3-8B-Instruct')" + }, + "teardown": { + "duration": 0.00013062497600913048, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-3.3-70B-Instruct]", + "lineno": 117, + "outcome": "passed", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.005876541952602565, + "outcome": "passed" + }, + "call": { + "duration": 7.561108374968171, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0004579999949783087, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.018791542039252818, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider together does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.0004900830099359155, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 117, + "outcome": "failed", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.0065952910808846354, + "outcome": "passed" + }, + "call": { + "duration": 2.6826554159633815, + "outcome": "failed", + "crash": { + "path": "/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py", + "lineno": 135, + "message": "IndexError: list index out of range" + }, + "traceback": [ + { + "path": "tests/verifications/openai/test_chat_completion.py", + "lineno": 135, + "message": "IndexError" + } + ], + "longrepr": "openai_client = \ninput_output = {'input': {'messages': [{'content': 'You are a helpful math tutor. Guide the user through the solution step by step.',... ['steps', 'final_answer'], 'title': 'MathReasoning', ...}}, 'type': 'json_schema'}}, 'output': 'valid_math_reasoning'}\ncorrect_model_name = 'meta-llama/Llama-4-Scout-17B-16E-Instruct'\n\n @pytest.mark.parametrize(\n \"model\",\n chat_completion_test_cases[\"test_chat_structured_output\"][\"test_params\"][\"model\"],\n )\n @pytest.mark.parametrize(\n \"input_output\",\n chat_completion_test_cases[\"test_chat_structured_output\"][\"test_params\"][\"input_output\"],\n )\n def test_chat_streaming_structured_output(openai_client, input_output, correct_model_name):\n response = openai_client.chat.completions.create(\n model=correct_model_name,\n messages=input_output[\"input\"][\"messages\"],\n response_format=input_output[\"input\"][\"response_format\"],\n stream=True,\n )\n maybe_json_content = \"\"\n for chunk in response:\n> maybe_json_content += chunk.choices[0].delta.content or \"\"\nE IndexError: list index out of range\n\ntests/verifications/openai/test_chat_completion.py:135: IndexError" + }, + "teardown": { + "duration": 0.0009669580031186342, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.019489208003506064, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider together does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.0007419160101562738, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 117, + "outcome": "failed", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output1-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.012299792026169598, + "outcome": "passed" + }, + "call": { + "duration": 2.829678333015181, + "outcome": "failed", + "crash": { + "path": "/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py", + "lineno": 135, + "message": "IndexError: list index out of range" + }, + "traceback": [ + { + "path": "tests/verifications/openai/test_chat_completion.py", + "lineno": 135, + "message": "IndexError" + } + ], + "longrepr": "openai_client = \ninput_output = {'input': {'messages': [{'content': 'You are a helpful math tutor. Guide the user through the solution step by step.',... ['steps', 'final_answer'], 'title': 'MathReasoning', ...}}, 'type': 'json_schema'}}, 'output': 'valid_math_reasoning'}\ncorrect_model_name = 'meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8'\n\n @pytest.mark.parametrize(\n \"model\",\n chat_completion_test_cases[\"test_chat_structured_output\"][\"test_params\"][\"model\"],\n )\n @pytest.mark.parametrize(\n \"input_output\",\n chat_completion_test_cases[\"test_chat_structured_output\"][\"test_params\"][\"input_output\"],\n )\n def test_chat_streaming_structured_output(openai_client, input_output, correct_model_name):\n response = openai_client.chat.completions.create(\n model=correct_model_name,\n messages=input_output[\"input\"][\"messages\"],\n response_format=input_output[\"input\"][\"response_format\"],\n stream=True,\n )\n maybe_json_content = \"\"\n for chunk in response:\n> maybe_json_content += chunk.choices[0].delta.content or \"\"\nE IndexError: list index out of range\n\ntests/verifications/openai/test_chat_completion.py:135: IndexError" + }, + "teardown": { + "duration": 0.0010418329620733857, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-gpt-4o]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-gpt-4o]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.016189916990697384, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider together does not support model gpt-4o')" + }, + "teardown": { + "duration": 0.00027966592460870743, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_streaming_structured_output[input_output1-gpt-4o-mini]", + "lineno": 117, + "outcome": "skipped", + "keywords": [ + "test_chat_streaming_structured_output[input_output1-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output1-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.010247125057503581, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 118, 'Skipped: Provider together does not support model gpt-4o-mini')" + }, + "teardown": { + "duration": 0.00023291702382266521, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-3.3-70B-Instruct]", + "lineno": 138, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_tool_calling[input_output0-Llama-3.3-70B-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-3.3-70B-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.012632582918740809, + "outcome": "passed" + }, + "call": { + "duration": 0.40774812502786517, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0007319580763578415, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Scout-17B-16E]", + "lineno": 138, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_tool_calling[input_output0-Llama-4-Scout-17B-16E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.019890791969373822, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 139, 'Skipped: Provider together does not support model Llama-4-Scout-17B-16E')" + }, + "teardown": { + "duration": 0.0006391670322045684, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "lineno": 138, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_tool_calling[input_output0-Llama-4-Scout-17B-16E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Scout-17B-16E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.0178165000397712, + "outcome": "passed" + }, + "call": { + "duration": 0.38229950005188584, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0010000420734286308, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Maverick-17B-128E]", + "lineno": 138, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_tool_calling[input_output0-Llama-4-Maverick-17B-128E]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.024259291938506067, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 139, 'Skipped: Provider together does not support model Llama-4-Maverick-17B-128E')" + }, + "teardown": { + "duration": 0.0003602079814299941, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "lineno": 138, + "outcome": "passed", + "keywords": [ + "test_chat_non_streaming_tool_calling[input_output0-Llama-4-Maverick-17B-128E-Instruct]", + "parametrize", + "pytestmark", + "input_output0-Llama-4-Maverick-17B-128E-Instruct", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.012425708002410829, + "outcome": "passed" + }, + "call": { + "duration": 0.7610744580160826, + "outcome": "passed" + }, + "teardown": { + "duration": 0.0005935420049354434, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-gpt-4o]", + "lineno": 138, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_tool_calling[input_output0-gpt-4o]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.018717541941441596, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 139, 'Skipped: Provider together does not support model gpt-4o')" + }, + "teardown": { + "duration": 0.000659791985526681, + "outcome": "passed" + } + }, + { + "nodeid": "tests/verifications/openai/test_chat_completion.py::test_chat_non_streaming_tool_calling[input_output0-gpt-4o-mini]", + "lineno": 138, + "outcome": "skipped", + "keywords": [ + "test_chat_non_streaming_tool_calling[input_output0-gpt-4o-mini]", + "parametrize", + "pytestmark", + "input_output0-gpt-4o-mini", + "test_chat_completion.py", + "openai", + "verifications", + "tests", + "llama-stack", + "" + ], + "setup": { + "duration": 0.012784749967977405, + "outcome": "skipped", + "longrepr": "('/Users/erichuang/projects/llama-stack/tests/verifications/openai/test_chat_completion.py', 139, 'Skipped: Provider together does not support model gpt-4o-mini')" + }, + "teardown": { + "duration": 0.0002145830076187849, + "outcome": "passed" + } + } + ] +} From 983f6feeb8eae327adb8273ecf0f309985fcf648 Mon Sep 17 00:00:00 2001 From: AlexHe99 Date: Wed, 9 Apr 2025 12:35:32 +0800 Subject: [PATCH 13/24] docs: Update remote-vllm.md with AMD GPU vLLM server supported. (#1858) Add the content to use AMD GPU as the vLLM server. Split the original part to two sub chapters, 1. AMD vLLM server 2. NVIDIA vLLM server (orignal) # What does this PR do? [Provide a short summary of what this PR does and why. Link to relevant issues if applicable.] [//]: # (If resolving an issue, uncomment and update the line below) [//]: # (Closes #[issue-number]) ## Test Plan [Describe the tests you ran to verify your changes with result summaries. *Provide clear instructions so the plan can be easily re-executed.*] [//]: # (## Documentation) --------- Signed-off-by: Alex He --- .../self_hosted_distro/remote-vllm.md | 74 +++++++++++++++++++ .../templates/remote-vllm/doc_template.md | 74 +++++++++++++++++++ 2 files changed, 148 insertions(+) diff --git a/docs/source/distributions/self_hosted_distro/remote-vllm.md b/docs/source/distributions/self_hosted_distro/remote-vllm.md index b6e8a8ad4..457d703b3 100644 --- a/docs/source/distributions/self_hosted_distro/remote-vllm.md +++ b/docs/source/distributions/self_hosted_distro/remote-vllm.md @@ -41,6 +41,80 @@ The following environment variables can be configured: ## Setting up vLLM server +Both AMD and NVIDIA GPUs can serve as accelerators for the vLLM server, which acts as both the LLM inference provider and the safety provider. + +### Setting up vLLM server on AMD GPU + +AMD provides two main vLLM container options: +- rocm/vllm: Production-ready container +- rocm/vllm-dev: Development container with the latest vLLM features + +Please check the [Blog about ROCm vLLM Usage](https://rocm.blogs.amd.com/software-tools-optimization/vllm-container/README.html) to get more details. + +Here is a sample script to start a ROCm vLLM server locally via Docker: + +```bash +export INFERENCE_PORT=8000 +export INFERENCE_MODEL=meta-llama/Llama-3.2-3B-Instruct +export CUDA_VISIBLE_DEVICES=0 +export VLLM_DIMG="rocm/vllm-dev:main" + +docker run \ + --pull always \ + --ipc=host \ + --privileged \ + --shm-size 16g \ + --device=/dev/kfd \ + --device=/dev/dri \ + --group-add video \ + --cap-add=SYS_PTRACE \ + --cap-add=CAP_SYS_ADMIN \ + --security-opt seccomp=unconfined \ + --security-opt apparmor=unconfined \ + --env "HUGGING_FACE_HUB_TOKEN=$HF_TOKEN" \ + --env "HIP_VISIBLE_DEVICES=$CUDA_VISIBLE_DEVICES" \ + -p $INFERENCE_PORT:$INFERENCE_PORT \ + -v ~/.cache/huggingface:/root/.cache/huggingface \ + $VLLM_DIMG \ + python -m vllm.entrypoints.openai.api_server \ + --model $INFERENCE_MODEL \ + --port $INFERENCE_PORT +``` + +Note that you'll also need to set `--enable-auto-tool-choice` and `--tool-call-parser` to [enable tool calling in vLLM](https://docs.vllm.ai/en/latest/features/tool_calling.html). + +If you are using Llama Stack Safety / Shield APIs, then you will need to also run another instance of a vLLM with a corresponding safety model like `meta-llama/Llama-Guard-3-1B` using a script like: + +```bash +export SAFETY_PORT=8081 +export SAFETY_MODEL=meta-llama/Llama-Guard-3-1B +export CUDA_VISIBLE_DEVICES=1 +export VLLM_DIMG="rocm/vllm-dev:main" + +docker run \ + --pull always \ + --ipc=host \ + --privileged \ + --shm-size 16g \ + --device=/dev/kfd \ + --device=/dev/dri \ + --group-add video \ + --cap-add=SYS_PTRACE \ + --cap-add=CAP_SYS_ADMIN \ + --security-opt seccomp=unconfined \ + --security-opt apparmor=unconfined \ + --env "HUGGING_FACE_HUB_TOKEN=$HF_TOKEN" \ + --env "HIP_VISIBLE_DEVICES=$CUDA_VISIBLE_DEVICES" \ + -p $SAFETY_PORT:$SAFETY_PORT \ + -v ~/.cache/huggingface:/root/.cache/huggingface \ + $VLLM_DIMG \ + python -m vllm.entrypoints.openai.api_server \ + --model $SAFETY_MODEL \ + --port $SAFETY_PORT +``` + +### Setting up vLLM server on NVIDIA GPU + Please check the [vLLM Documentation](https://docs.vllm.ai/en/v0.5.5/serving/deploying_with_docker.html) to get a vLLM endpoint. Here is a sample script to start a vLLM server locally via Docker: ```bash diff --git a/llama_stack/templates/remote-vllm/doc_template.md b/llama_stack/templates/remote-vllm/doc_template.md index 57c9f116c..7543e8239 100644 --- a/llama_stack/templates/remote-vllm/doc_template.md +++ b/llama_stack/templates/remote-vllm/doc_template.md @@ -28,6 +28,80 @@ The following environment variables can be configured: ## Setting up vLLM server +Both AMD and NVIDIA GPUs can serve as accelerators for the vLLM server, which acts as both the LLM inference provider and the safety provider. + +### Setting up vLLM server on AMD GPU + +AMD provides two main vLLM container options: +- rocm/vllm: Production-ready container +- rocm/vllm-dev: Development container with the latest vLLM features + +Please check the [Blog about ROCm vLLM Usage](https://rocm.blogs.amd.com/software-tools-optimization/vllm-container/README.html) to get more details. + +Here is a sample script to start a ROCm vLLM server locally via Docker: + +```bash +export INFERENCE_PORT=8000 +export INFERENCE_MODEL=meta-llama/Llama-3.2-3B-Instruct +export CUDA_VISIBLE_DEVICES=0 +export VLLM_DIMG="rocm/vllm-dev:main" + +docker run \ + --pull always \ + --ipc=host \ + --privileged \ + --shm-size 16g \ + --device=/dev/kfd \ + --device=/dev/dri \ + --group-add video \ + --cap-add=SYS_PTRACE \ + --cap-add=CAP_SYS_ADMIN \ + --security-opt seccomp=unconfined \ + --security-opt apparmor=unconfined \ + --env "HUGGING_FACE_HUB_TOKEN=$HF_TOKEN" \ + --env "HIP_VISIBLE_DEVICES=$CUDA_VISIBLE_DEVICES" \ + -p $INFERENCE_PORT:$INFERENCE_PORT \ + -v ~/.cache/huggingface:/root/.cache/huggingface \ + $VLLM_DIMG \ + python -m vllm.entrypoints.openai.api_server \ + --model $INFERENCE_MODEL \ + --port $INFERENCE_PORT +``` + +Note that you'll also need to set `--enable-auto-tool-choice` and `--tool-call-parser` to [enable tool calling in vLLM](https://docs.vllm.ai/en/latest/features/tool_calling.html). + +If you are using Llama Stack Safety / Shield APIs, then you will need to also run another instance of a vLLM with a corresponding safety model like `meta-llama/Llama-Guard-3-1B` using a script like: + +```bash +export SAFETY_PORT=8081 +export SAFETY_MODEL=meta-llama/Llama-Guard-3-1B +export CUDA_VISIBLE_DEVICES=1 +export VLLM_DIMG="rocm/vllm-dev:main" + +docker run \ + --pull always \ + --ipc=host \ + --privileged \ + --shm-size 16g \ + --device=/dev/kfd \ + --device=/dev/dri \ + --group-add video \ + --cap-add=SYS_PTRACE \ + --cap-add=CAP_SYS_ADMIN \ + --security-opt seccomp=unconfined \ + --security-opt apparmor=unconfined \ + --env "HUGGING_FACE_HUB_TOKEN=$HF_TOKEN" \ + --env "HIP_VISIBLE_DEVICES=$CUDA_VISIBLE_DEVICES" \ + -p $SAFETY_PORT:$SAFETY_PORT \ + -v ~/.cache/huggingface:/root/.cache/huggingface \ + $VLLM_DIMG \ + python -m vllm.entrypoints.openai.api_server \ + --model $SAFETY_MODEL \ + --port $SAFETY_PORT +``` + +### Setting up vLLM server on NVIDIA GPU + Please check the [vLLM Documentation](https://docs.vllm.ai/en/v0.5.5/serving/deploying_with_docker.html) to get a vLLM endpoint. Here is a sample script to start a vLLM server locally via Docker: ```bash From 10882bf478bcb8d89babeb9416ab24fb39385d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Wed, 9 Apr 2025 09:43:48 +0200 Subject: [PATCH 14/24] chore: remove unused tempdir in agent (#1896) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # What does this PR do? The usage of the tempdir was removed in 094eb6a5ae8dbac297fe59914db11c612250f92f. Signed-off-by: Sébastien Han --- .../providers/inline/agents/meta_reference/agent_instance.py | 2 -- llama_stack/providers/inline/agents/meta_reference/agents.py | 3 --- 2 files changed, 5 deletions(-) diff --git a/llama_stack/providers/inline/agents/meta_reference/agent_instance.py b/llama_stack/providers/inline/agents/meta_reference/agent_instance.py index 6840da89f..f441d6eb6 100644 --- a/llama_stack/providers/inline/agents/meta_reference/agent_instance.py +++ b/llama_stack/providers/inline/agents/meta_reference/agent_instance.py @@ -89,7 +89,6 @@ class ChatAgent(ShieldRunnerMixin): self, agent_id: str, agent_config: AgentConfig, - tempdir: str, inference_api: Inference, safety_api: Safety, tool_runtime_api: ToolRuntime, @@ -99,7 +98,6 @@ class ChatAgent(ShieldRunnerMixin): ): self.agent_id = agent_id self.agent_config = agent_config - self.tempdir = tempdir self.inference_api = inference_api self.safety_api = safety_api self.vector_io_api = vector_io_api diff --git a/llama_stack/providers/inline/agents/meta_reference/agents.py b/llama_stack/providers/inline/agents/meta_reference/agents.py index 5ca123595..656178773 100644 --- a/llama_stack/providers/inline/agents/meta_reference/agents.py +++ b/llama_stack/providers/inline/agents/meta_reference/agents.py @@ -7,7 +7,6 @@ import json import logging import shutil -import tempfile import uuid from typing import AsyncGenerator, List, Optional, Union @@ -64,7 +63,6 @@ class MetaReferenceAgentsImpl(Agents): self.tool_groups_api = tool_groups_api self.in_memory_store = InmemoryKVStoreImpl() - self.tempdir = tempfile.mkdtemp() async def initialize(self) -> None: self.persistence_store = await kvstore_impl(self.config.persistence_store) @@ -107,7 +105,6 @@ class MetaReferenceAgentsImpl(Agents): return ChatAgent( agent_id=agent_id, agent_config=agent_config, - tempdir=self.tempdir, inference_api=self.inference_api, safety_api=self.safety_api, vector_io_api=self.vector_io_api, From 8001c30a4fe12ca15c79ca7b7038c30d1d7b181f Mon Sep 17 00:00:00 2001 From: Ashwin Bharambe Date: Wed, 9 Apr 2025 00:46:02 -0700 Subject: [PATCH 15/24] fix: meta reference + llama4 tokenizer fix --- llama_stack/models/llama/llama4/tokenizer.py | 4 +++- .../providers/inline/inference/meta_reference/generators.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/llama_stack/models/llama/llama4/tokenizer.py b/llama_stack/models/llama/llama4/tokenizer.py index 4d271e5fd..8eabc3205 100644 --- a/llama_stack/models/llama/llama4/tokenizer.py +++ b/llama_stack/models/llama/llama4/tokenizer.py @@ -56,9 +56,11 @@ LLAMA4_TEXT_POST_TRAIN_SPECIAL_TOKENS = [ "<|text_post_train_reserved_special_token_3|>", "<|text_post_train_reserved_special_token_4|>", "<|text_post_train_reserved_special_token_5|>", + "<|text_post_train_reserved_special_token_6|>", + "<|text_post_train_reserved_special_token_7|>", "<|finetune_right_pad|>", ] + get_reserved_special_tokens( - "text_post_train", 61, 6 + "text_post_train", 61, 8 ) # <|text_post_train_reserved_special_token_6|>, ..., <|text_post_train_reserved_special_token_66|> # 200080, ..., 201133 diff --git a/llama_stack/providers/inline/inference/meta_reference/generators.py b/llama_stack/providers/inline/inference/meta_reference/generators.py index 65bed4d8c..34dd58a9a 100644 --- a/llama_stack/providers/inline/inference/meta_reference/generators.py +++ b/llama_stack/providers/inline/inference/meta_reference/generators.py @@ -259,7 +259,7 @@ class Llama3Generator: temperature, top_p = _infer_sampling_params(sampling_params) for result in self.inner_generator.generate( - llm_inputs=[self.formatter.encode_content(request.content)], + model_inputs=[self.formatter.encode_content(request.content)], max_gen_len=max_gen_len, temperature=temperature, top_p=top_p, @@ -284,7 +284,7 @@ class Llama3Generator: temperature, top_p = _infer_sampling_params(sampling_params) for result in self.inner_generator.generate( - llm_inputs=[self.formatter.encode_dialog_prompt(request.messages, _infer_tool_prompt_format(request))], + model_inputs=[self.formatter.encode_dialog_prompt(request.messages, _infer_tool_prompt_format(request))], max_gen_len=max_gen_len, temperature=temperature, top_p=top_p, From e3d22d8de733b1786087fba85920695c40e15777 Mon Sep 17 00:00:00 2001 From: Ihar Hrachyshka Date: Wed, 9 Apr 2025 04:10:07 -0400 Subject: [PATCH 16/24] chore: fix hash for thollander/actions-comment-pull-request (#1900) # What does this PR do? Fix hash for v3.0.1 tag for a github action. Signed-off-by: Ihar Hrachyshka --- .github/workflows/gha_workflow_llama_stack_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gha_workflow_llama_stack_tests.yml b/.github/workflows/gha_workflow_llama_stack_tests.yml index 91b9d2f3b..9eae291e9 100644 --- a/.github/workflows/gha_workflow_llama_stack_tests.yml +++ b/.github/workflows/gha_workflow_llama_stack_tests.yml @@ -320,7 +320,7 @@ jobs: - name: "PR - Update comment" id: pr_update_comment if: github.event_name == 'pull_request_target' - uses: thollander/actions-comment-pull-request@65f9e5c9a1f2cd378bd74b2e057c9736982a8e74 # v3.0.1 + uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1 with: filePath: test-summary.md From 45e210fd0c43ee76a93f21275575f3cbd83a70f6 Mon Sep 17 00:00:00 2001 From: Ashwin Bharambe Date: Wed, 9 Apr 2025 01:09:16 -0700 Subject: [PATCH 17/24] fix: llama3 bf16 model load --- llama_stack/models/llama/llama3/generation.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/llama_stack/models/llama/llama3/generation.py b/llama_stack/models/llama/llama3/generation.py index ee99a07ba..8c6aa242b 100644 --- a/llama_stack/models/llama/llama3/generation.py +++ b/llama_stack/models/llama/llama3/generation.py @@ -119,17 +119,16 @@ class Llama3: torch.set_default_device(device) else: print(f"Setting default device to {device}") - torch.set_default_device(device) if device.type == "cuda": if torch.cuda.is_bf16_supported(): - torch.set_default_dtype(torch.bfloat16) + torch.set_default_tensor_type(torch.cuda.BFloat16Tensor) else: - torch.set_default_dtype(torch.half) + torch.set_default_tensor_type(torch.cuda.Float16Tensor) elif device.type == "xpu": if torch.xpu.is_bf16_supported(): - torch.set_default_dtype(torch.bfloat16) + torch.set_default_tensor_type(torch.xpu.BFloat16Tensor) else: - torch.set_default_dtype(torch.half) + torch.set_default_tensor_type(torch.xpu.Float16Tensor) model = build_model() print("Loading state dict...") From 389767010b0333c49cf6cb86122308a5ec474621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Wed, 9 Apr 2025 10:30:41 +0200 Subject: [PATCH 18/24] feat: ability to execute external providers (#1672) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # What does this PR do? Providers that live outside of the llama-stack codebase are now supported. A new property `external_providers_dir` has been added to the main config and can be configured as follow: ``` external_providers_dir: /etc/llama-stack/providers.d/ ``` Where the expected structure is: ``` providers.d/ inference/ custom_ollama.yaml vllm.yaml vector_io/ qdrant.yaml ``` Where `custom_ollama.yaml` is: ``` adapter: adapter_type: custom_ollama pip_packages: ["ollama", "aiohttp"] config_class: llama_stack_ollama_provider.config.OllamaImplConfig module: llama_stack_ollama_provider api_dependencies: [] optional_api_dependencies: [] ``` Obviously the package must be installed on the system, here is the `llama_stack_ollama_provider` example: ``` $ uv pip show llama-stack-ollama-provider Using Python 3.10.16 environment at: /Users/leseb/Documents/AI/llama-stack/.venv Name: llama-stack-ollama-provider Version: 0.1.0 Location: /Users/leseb/Documents/AI/llama-stack/.venv/lib/python3.10/site-packages Editable project location: /private/var/folders/mq/rnm5w_7s2d3fxmtkx02knvhm0000gn/T/tmp.ZBHU5Ezxg4/ollama/llama-stack-ollama-provider Requires: Required-by: ``` Closes: https://github.com/meta-llama/llama-stack/issues/658 Signed-off-by: Sébastien Han --- .github/workflows/test-external-providers.yml | 93 +++++++ docs/source/providers/external.md | 234 ++++++++++++++++++ docs/source/providers/index.md | 5 + llama_stack/distribution/datatypes.py | 5 + llama_stack/distribution/distribution.py | 130 +++++++++- llama_stack/distribution/resolver.py | 1 + llama_stack/distribution/stack.py | 2 +- .../llama-stack-provider-ollama/README.md | 3 + .../custom_ollama.yaml | 7 + .../pyproject.toml | 44 ++++ .../llama-stack-provider-ollama/run.yaml | 135 ++++++++++ tests/unit/distribution/test_distribution.py | 223 +++++++++++++++++ 12 files changed, 875 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/test-external-providers.yml create mode 100644 docs/source/providers/external.md create mode 100644 tests/external-provider/llama-stack-provider-ollama/README.md create mode 100644 tests/external-provider/llama-stack-provider-ollama/custom_ollama.yaml create mode 100644 tests/external-provider/llama-stack-provider-ollama/pyproject.toml create mode 100644 tests/external-provider/llama-stack-provider-ollama/run.yaml create mode 100644 tests/unit/distribution/test_distribution.py diff --git a/.github/workflows/test-external-providers.yml b/.github/workflows/test-external-providers.yml new file mode 100644 index 000000000..2ead8f845 --- /dev/null +++ b/.github/workflows/test-external-providers.yml @@ -0,0 +1,93 @@ +name: Test External Providers + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test-external-providers: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + python-version: "3.10" + + - name: Install Ollama + run: | + curl -fsSL https://ollama.com/install.sh | sh + + - name: Pull Ollama image + run: | + ollama pull llama3.2:3b-instruct-fp16 + + - name: Start Ollama in background + run: | + nohup ollama run llama3.2:3b-instruct-fp16 --keepalive=30m > ollama.log 2>&1 & + + - name: Set Up Environment and Install Dependencies + run: | + uv sync --extra dev --extra test + uv pip install -e . + + - name: Install Ollama custom provider + run: | + mkdir -p tests/external-provider/llama-stack-provider-ollama/src/ + cp -a llama_stack/providers/remote/inference/ollama/ tests/external-provider/llama-stack-provider-ollama/src/llama_stack_provider_ollama + uv pip install tests/external-provider/llama-stack-provider-ollama + + - name: Create provider configuration + run: | + mkdir -p /tmp/providers.d/remote/inference + cp tests/external-provider/llama-stack-provider-ollama/custom_ollama.yaml /tmp/providers.d/remote/inference/custom_ollama.yaml + + - name: Wait for Ollama to start + run: | + echo "Waiting for Ollama..." + for i in {1..30}; do + if curl -s http://localhost:11434 | grep -q "Ollama is running"; then + echo "Ollama is running!" + exit 0 + fi + sleep 1 + done + echo "Ollama failed to start" + ollama ps + ollama.log + exit 1 + + - name: Start Llama Stack server in background + env: + INFERENCE_MODEL: "meta-llama/Llama-3.2-3B-Instruct" + run: | + source .venv/bin/activate + nohup uv run llama stack run tests/external-provider/llama-stack-provider-ollama/run.yaml --image-type venv > server.log 2>&1 & + + - name: Wait for Llama Stack server to be ready + run: | + echo "Waiting for Llama Stack server..." + for i in {1..30}; do + if curl -s http://localhost:8321/v1/health | grep -q "OK"; then + echo "Llama Stack server is up!" + if grep -q "remote::custom_ollama from /tmp/providers.d/remote/inference/custom_ollama.yaml" server.log; then + echo "Llama Stack server is using custom Ollama provider" + exit 0 + else + echo "Llama Stack server is not using custom Ollama provider" + exit 1 + fi + fi + sleep 1 + done + echo "Llama Stack server failed to start" + cat server.log + exit 1 + + - name: run inference tests + run: | + uv run pytest -v tests/integration/inference/test_text_inference.py --stack-config="http://localhost:8321" --text-model="meta-llama/Llama-3.2-3B-Instruct" --embedding-model=all-MiniLM-L6-v2 diff --git a/docs/source/providers/external.md b/docs/source/providers/external.md new file mode 100644 index 000000000..90fc77979 --- /dev/null +++ b/docs/source/providers/external.md @@ -0,0 +1,234 @@ +# External Providers + +Llama Stack supports external providers that live outside of the main codebase. This allows you to: +- Create and maintain your own providers independently +- Share providers with others without contributing to the main codebase +- Keep provider-specific code separate from the core Llama Stack code + +## Configuration + +To enable external providers, you need to configure the `external_providers_dir` in your Llama Stack configuration. This directory should contain your external provider specifications: + +```yaml +external_providers_dir: /etc/llama-stack/providers.d/ +``` + +## Directory Structure + +The external providers directory should follow this structure: + +``` +providers.d/ + remote/ + inference/ + custom_ollama.yaml + vllm.yaml + vector_io/ + qdrant.yaml + safety/ + llama-guard.yaml + inline/ + inference/ + custom_ollama.yaml + vllm.yaml + vector_io/ + qdrant.yaml + safety/ + llama-guard.yaml +``` + +Each YAML file in these directories defines a provider specification for that particular API. + +## Provider Types + +Llama Stack supports two types of external providers: + +1. **Remote Providers**: Providers that communicate with external services (e.g., cloud APIs) +2. **Inline Providers**: Providers that run locally within the Llama Stack process + +## Known External Providers + +Here's a list of known external providers that you can use with Llama Stack: + +| Type | Name | Description | Repository | +|------|------|-------------|------------| +| Remote | KubeFlow Training | Train models with KubeFlow | [llama-stack-provider-kft](https://github.com/opendatahub-io/llama-stack-provider-kft) | + +### Remote Provider Specification + +Remote providers are used when you need to communicate with external services. Here's an example for a custom Ollama provider: + +```yaml +adapter: + adapter_type: custom_ollama + pip_packages: + - ollama + - aiohttp + config_class: llama_stack_ollama_provider.config.OllamaImplConfig + module: llama_stack_ollama_provider +api_dependencies: [] +optional_api_dependencies: [] +``` + +#### Adapter Configuration + +The `adapter` section defines how to load and configure the provider: + +- `adapter_type`: A unique identifier for this adapter +- `pip_packages`: List of Python packages required by the provider +- `config_class`: The full path to the configuration class +- `module`: The Python module containing the provider implementation + +### Inline Provider Specification + +Inline providers run locally within the Llama Stack process. Here's an example for a custom vector store provider: + +```yaml +module: llama_stack_vector_provider +config_class: llama_stack_vector_provider.config.VectorStoreConfig +pip_packages: + - faiss-cpu + - numpy +api_dependencies: + - inference +optional_api_dependencies: + - vector_io +provider_data_validator: llama_stack_vector_provider.validator.VectorStoreValidator +container_image: custom-vector-store:latest # optional +``` + +#### Inline Provider Fields + +- `module`: The Python module containing the provider implementation +- `config_class`: The full path to the configuration class +- `pip_packages`: List of Python packages required by the provider +- `api_dependencies`: List of Llama Stack APIs that this provider depends on +- `optional_api_dependencies`: List of optional Llama Stack APIs that this provider can use +- `provider_data_validator`: Optional validator for provider data +- `container_image`: Optional container image to use instead of pip packages + +## Required Implementation + +### Remote Providers + +Remote providers must expose a `get_adapter_impl()` function in their module that takes two arguments: +1. `config`: An instance of the provider's config class +2. `deps`: A dictionary of API dependencies + +This function must return an instance of the provider's adapter class that implements the required protocol for the API. + +Example: +```python +async def get_adapter_impl( + config: OllamaImplConfig, deps: Dict[Api, Any] +) -> OllamaInferenceAdapter: + return OllamaInferenceAdapter(config) +``` + +### Inline Providers + +Inline providers must expose a `get_provider_impl()` function in their module that takes two arguments: +1. `config`: An instance of the provider's config class +2. `deps`: A dictionary of API dependencies + +Example: +```python +async def get_provider_impl( + config: VectorStoreConfig, deps: Dict[Api, Any] +) -> VectorStoreImpl: + impl = VectorStoreImpl(config, deps[Api.inference]) + await impl.initialize() + return impl +``` + +## Dependencies + +The provider package must be installed on the system. For example: + +```bash +$ uv pip show llama-stack-ollama-provider +Name: llama-stack-ollama-provider +Version: 0.1.0 +Location: /path/to/venv/lib/python3.10/site-packages +``` + +## Example: Custom Ollama Provider + +Here's a complete example of creating and using a custom Ollama provider: + +1. First, create the provider package: + +```bash +mkdir -p llama-stack-provider-ollama +cd llama-stack-provider-ollama +git init +uv init +``` + +2. Edit `pyproject.toml`: + +```toml +[project] +name = "llama-stack-provider-ollama" +version = "0.1.0" +description = "Ollama provider for Llama Stack" +requires-python = ">=3.10" +dependencies = ["llama-stack", "pydantic", "ollama", "aiohttp"] +``` + +3. Create the provider specification: + +```yaml +# /etc/llama-stack/providers.d/remote/inference/custom_ollama.yaml +adapter: + adapter_type: custom_ollama + pip_packages: ["ollama", "aiohttp"] + config_class: llama_stack_provider_ollama.config.OllamaImplConfig + module: llama_stack_provider_ollama +api_dependencies: [] +optional_api_dependencies: [] +``` + +4. Install the provider: + +```bash +uv pip install -e . +``` + +5. Configure Llama Stack to use external providers: + +```yaml +external_providers_dir: /etc/llama-stack/providers.d/ +``` + +The provider will now be available in Llama Stack with the type `remote::custom_ollama`. + +## Best Practices + +1. **Package Naming**: Use the prefix `llama-stack-provider-` for your provider packages to make them easily identifiable. + +2. **Version Management**: Keep your provider package versioned and compatible with the Llama Stack version you're using. + +3. **Dependencies**: Only include the minimum required dependencies in your provider package. + +4. **Documentation**: Include clear documentation in your provider package about: + - Installation requirements + - Configuration options + - Usage examples + - Any limitations or known issues + +5. **Testing**: Include tests in your provider package to ensure it works correctly with Llama Stack. +You can refer to the [integration tests +guide](https://github.com/meta-llama/llama-stack/blob/main/tests/integration/README.md) for more +information. Execute the test for the Provider type you are developing. + +## Troubleshooting + +If your external provider isn't being loaded: + +1. Check that the `external_providers_dir` path is correct and accessible. +2. Verify that the YAML files are properly formatted. +3. Ensure all required Python packages are installed. +4. Check the Llama Stack server logs for any error messages - turn on debug logging to get more + information using `LLAMA_STACK_LOGGING=all=debug`. +5. Verify that the provider package is installed in your Python environment. diff --git a/docs/source/providers/index.md b/docs/source/providers/index.md index f8997a281..75faf7c00 100644 --- a/docs/source/providers/index.md +++ b/docs/source/providers/index.md @@ -11,6 +11,10 @@ Providers come in two flavors: Importantly, Llama Stack always strives to provide at least one fully inline provider for each API so you can iterate on a fully featured environment locally. +## External Providers + +Llama Stack supports external providers that live outside of the main codebase. This allows you to create and maintain your own providers independently. See the [External Providers Guide](external) for details. + ## Agents Run multi-step agentic workflows with LLMs with tool usage, memory (RAG), etc. @@ -50,6 +54,7 @@ The following providers (i.e., databases) are available for Vector IO: ```{toctree} :maxdepth: 1 +external vector_io/faiss vector_io/sqlite-vec vector_io/chromadb diff --git a/llama_stack/distribution/datatypes.py b/llama_stack/distribution/datatypes.py index 48f1925dd..b24b0ec50 100644 --- a/llama_stack/distribution/datatypes.py +++ b/llama_stack/distribution/datatypes.py @@ -312,6 +312,11 @@ a default SQLite store will be used.""", description="Configuration for the HTTP(S) server", ) + external_providers_dir: Optional[str] = Field( + default=None, + description="Path to directory containing external provider implementations. The providers code and dependencies must be installed on the system.", + ) + class BuildConfig(BaseModel): version: str = LLAMA_STACK_BUILD_CONFIG_VERSION diff --git a/llama_stack/distribution/distribution.py b/llama_stack/distribution/distribution.py index ddb727663..d4447139c 100644 --- a/llama_stack/distribution/distribution.py +++ b/llama_stack/distribution/distribution.py @@ -4,12 +4,25 @@ # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. +import glob import importlib -from typing import Dict, List +import os +from typing import Any, Dict, List +import yaml from pydantic import BaseModel -from llama_stack.providers.datatypes import Api, ProviderSpec +from llama_stack.distribution.datatypes import StackRunConfig +from llama_stack.log import get_logger +from llama_stack.providers.datatypes import ( + AdapterSpec, + Api, + InlineProviderSpec, + ProviderSpec, + remote_provider_spec, +) + +logger = get_logger(name=__name__, category="core") def stack_apis() -> List[Api]: @@ -59,11 +72,116 @@ def providable_apis() -> List[Api]: return [api for api in Api if api not in routing_table_apis and api != Api.inspect and api != Api.providers] -def get_provider_registry() -> Dict[Api, Dict[str, ProviderSpec]]: - ret = {} +def _load_remote_provider_spec(spec_data: Dict[str, Any], api: Api) -> ProviderSpec: + adapter = AdapterSpec(**spec_data["adapter"]) + spec = remote_provider_spec( + api=api, + adapter=adapter, + api_dependencies=[Api(dep) for dep in spec_data.get("api_dependencies", [])], + ) + return spec + + +def _load_inline_provider_spec(spec_data: Dict[str, Any], api: Api, provider_name: str) -> ProviderSpec: + spec = InlineProviderSpec( + api=api, + provider_type=f"inline::{provider_name}", + pip_packages=spec_data.get("pip_packages", []), + module=spec_data["module"], + config_class=spec_data["config_class"], + api_dependencies=[Api(dep) for dep in spec_data.get("api_dependencies", [])], + optional_api_dependencies=[Api(dep) for dep in spec_data.get("optional_api_dependencies", [])], + provider_data_validator=spec_data.get("provider_data_validator"), + container_image=spec_data.get("container_image"), + ) + return spec + + +def get_provider_registry(config: StackRunConfig | None = None) -> Dict[Api, Dict[str, ProviderSpec]]: + """Get the provider registry, optionally including external providers. + + This function loads both built-in providers and external providers from YAML files. + External providers are loaded from a directory structure like: + + providers.d/ + remote/ + inference/ + custom_ollama.yaml + vllm.yaml + vector_io/ + qdrant.yaml + safety/ + llama-guard.yaml + inline/ + inference/ + custom_ollama.yaml + vllm.yaml + vector_io/ + qdrant.yaml + safety/ + llama-guard.yaml + + Args: + config: Optional StackRunConfig containing the external providers directory path + + Returns: + A dictionary mapping APIs to their available providers + + Raises: + FileNotFoundError: If the external providers directory doesn't exist + ValueError: If any provider spec is invalid + """ + + ret: Dict[Api, Dict[str, ProviderSpec]] = {} for api in providable_apis(): name = api.name.lower() - module = importlib.import_module(f"llama_stack.providers.registry.{name}") - ret[api] = {a.provider_type: a for a in module.available_providers()} + logger.debug(f"Importing module {name}") + try: + module = importlib.import_module(f"llama_stack.providers.registry.{name}") + ret[api] = {a.provider_type: a for a in module.available_providers()} + except ImportError as e: + logger.warning(f"Failed to import module {name}: {e}") + if config and config.external_providers_dir: + external_providers_dir = os.path.abspath(config.external_providers_dir) + if not os.path.exists(external_providers_dir): + raise FileNotFoundError(f"External providers directory not found: {external_providers_dir}") + logger.info(f"Loading external providers from {external_providers_dir}") + + for api in providable_apis(): + api_name = api.name.lower() + + # Process both remote and inline providers + for provider_type in ["remote", "inline"]: + api_dir = os.path.join(external_providers_dir, provider_type, api_name) + if not os.path.exists(api_dir): + logger.debug(f"No {provider_type} provider directory found for {api_name}") + continue + + # Look for provider spec files in the API directory + for spec_path in glob.glob(os.path.join(api_dir, "*.yaml")): + provider_name = os.path.splitext(os.path.basename(spec_path))[0] + logger.info(f"Loading {provider_type} provider spec from {spec_path}") + + try: + with open(spec_path) as f: + spec_data = yaml.safe_load(f) + + if provider_type == "remote": + spec = _load_remote_provider_spec(spec_data, api) + provider_type_key = f"remote::{provider_name}" + else: + spec = _load_inline_provider_spec(spec_data, api, provider_name) + provider_type_key = f"inline::{provider_name}" + + logger.info(f"Loaded {provider_type} provider spec for {provider_type_key} from {spec_path}") + if provider_type_key in ret[api]: + logger.warning(f"Overriding already registered provider {provider_type_key} for {api.name}") + ret[api][provider_type_key] = spec + except yaml.YAMLError as yaml_err: + logger.error(f"Failed to parse YAML file {spec_path}: {yaml_err}") + raise yaml_err + except Exception as e: + logger.error(f"Failed to load provider spec from {spec_path}: {e}") + raise e return ret diff --git a/llama_stack/distribution/resolver.py b/llama_stack/distribution/resolver.py index 25fe3f184..33ad343ec 100644 --- a/llama_stack/distribution/resolver.py +++ b/llama_stack/distribution/resolver.py @@ -351,6 +351,7 @@ async def instantiate_provider( if not hasattr(provider_spec, "module"): raise AttributeError(f"ProviderSpec of type {type(provider_spec)} does not have a 'module' attribute") + logger.debug(f"Instantiating provider {provider.provider_id} from {provider_spec.module}") module = importlib.import_module(provider_spec.module) args = [] if isinstance(provider_spec, RemoteProviderSpec): diff --git a/llama_stack/distribution/stack.py b/llama_stack/distribution/stack.py index 9c9289a77..d70878db4 100644 --- a/llama_stack/distribution/stack.py +++ b/llama_stack/distribution/stack.py @@ -218,7 +218,7 @@ async def construct_stack( run_config: StackRunConfig, provider_registry: Optional[ProviderRegistry] = None ) -> Dict[Api, Any]: dist_registry, _ = await create_dist_registry(run_config.metadata_store, run_config.image_name) - impls = await resolve_impls(run_config, provider_registry or get_provider_registry(), dist_registry) + impls = await resolve_impls(run_config, provider_registry or get_provider_registry(run_config), dist_registry) await register_resources(run_config, impls) return impls diff --git a/tests/external-provider/llama-stack-provider-ollama/README.md b/tests/external-provider/llama-stack-provider-ollama/README.md new file mode 100644 index 000000000..8bd2b6a87 --- /dev/null +++ b/tests/external-provider/llama-stack-provider-ollama/README.md @@ -0,0 +1,3 @@ +# Ollama external provider for Llama Stack + +Template code to create a new external provider for Llama Stack. diff --git a/tests/external-provider/llama-stack-provider-ollama/custom_ollama.yaml b/tests/external-provider/llama-stack-provider-ollama/custom_ollama.yaml new file mode 100644 index 000000000..f0960b4d8 --- /dev/null +++ b/tests/external-provider/llama-stack-provider-ollama/custom_ollama.yaml @@ -0,0 +1,7 @@ +adapter: + adapter_type: custom_ollama + pip_packages: ["ollama", "aiohttp"] + config_class: llama_stack_provider_ollama.config.OllamaImplConfig + module: llama_stack_provider_ollama +api_dependencies: [] +optional_api_dependencies: [] diff --git a/tests/external-provider/llama-stack-provider-ollama/pyproject.toml b/tests/external-provider/llama-stack-provider-ollama/pyproject.toml new file mode 100644 index 000000000..ddebc54b0 --- /dev/null +++ b/tests/external-provider/llama-stack-provider-ollama/pyproject.toml @@ -0,0 +1,44 @@ +[project] +dependencies = [ + "llama-stack", + "pydantic", + "ollama", + "aiohttp", + "aiosqlite", + "autoevals", + "blobfile", + "chardet", + "chromadb-client", + "datasets", + "faiss-cpu", + "fastapi", + "fire", + "httpx", + "matplotlib", + "mcp", + "nltk", + "numpy", + "openai", + "opentelemetry-exporter-otlp-proto-http", + "opentelemetry-sdk", + "pandas", + "pillow", + "psycopg2-binary", + "pymongo", + "pypdf", + "redis", + "requests", + "scikit-learn", + "scipy", + "sentencepiece", + "tqdm", + "transformers", + "tree_sitter", + "uvicorn", +] + +name = "llama-stack-provider-ollama" +version = "0.1.0" +description = "External provider for Ollama using the Llama Stack API" +readme = "README.md" +requires-python = ">=3.10" diff --git a/tests/external-provider/llama-stack-provider-ollama/run.yaml b/tests/external-provider/llama-stack-provider-ollama/run.yaml new file mode 100644 index 000000000..7a3636c4d --- /dev/null +++ b/tests/external-provider/llama-stack-provider-ollama/run.yaml @@ -0,0 +1,135 @@ +version: '2' +image_name: ollama +apis: +- agents +- datasetio +- eval +- inference +- safety +- scoring +- telemetry +- tool_runtime +- vector_io +providers: + inference: + - provider_id: custom_ollama + provider_type: remote::custom_ollama + config: + url: ${env.OLLAMA_URL:http://localhost:11434} + vector_io: + - provider_id: faiss + provider_type: inline::faiss + config: + kvstore: + type: sqlite + namespace: null + db_path: ${env.SQLITE_STORE_DIR:~/.llama/distributions/ollama}/faiss_store.db + safety: + - provider_id: llama-guard + provider_type: inline::llama-guard + config: + excluded_categories: [] + agents: + - provider_id: meta-reference + provider_type: inline::meta-reference + config: + persistence_store: + type: sqlite + namespace: null + db_path: ${env.SQLITE_STORE_DIR:~/.llama/distributions/ollama}/agents_store.db + telemetry: + - provider_id: meta-reference + provider_type: inline::meta-reference + config: + service_name: ${env.OTEL_SERVICE_NAME:llama-stack} + sinks: ${env.TELEMETRY_SINKS:console,sqlite} + sqlite_db_path: ${env.SQLITE_DB_PATH:~/.llama/distributions/ollama/trace_store.db} + eval: + - provider_id: meta-reference + provider_type: inline::meta-reference + config: + kvstore: + type: sqlite + namespace: null + db_path: ${env.SQLITE_STORE_DIR:~/.llama/distributions/ollama}/meta_reference_eval.db + datasetio: + - provider_id: huggingface + provider_type: remote::huggingface + config: + kvstore: + type: sqlite + namespace: null + db_path: ${env.SQLITE_STORE_DIR:~/.llama/distributions/ollama}/huggingface_datasetio.db + - provider_id: localfs + provider_type: inline::localfs + config: + kvstore: + type: sqlite + namespace: null + db_path: ${env.SQLITE_STORE_DIR:~/.llama/distributions/ollama}/localfs_datasetio.db + scoring: + - provider_id: basic + provider_type: inline::basic + config: {} + - provider_id: llm-as-judge + provider_type: inline::llm-as-judge + config: {} + - provider_id: braintrust + provider_type: inline::braintrust + config: + openai_api_key: ${env.OPENAI_API_KEY:} + tool_runtime: + - provider_id: brave-search + provider_type: remote::brave-search + config: + api_key: ${env.BRAVE_SEARCH_API_KEY:} + max_results: 3 + - provider_id: tavily-search + provider_type: remote::tavily-search + config: + api_key: ${env.TAVILY_SEARCH_API_KEY:} + max_results: 3 + - provider_id: code-interpreter + provider_type: inline::code-interpreter + config: {} + - provider_id: rag-runtime + provider_type: inline::rag-runtime + config: {} + - provider_id: model-context-protocol + provider_type: remote::model-context-protocol + config: {} + - provider_id: wolfram-alpha + provider_type: remote::wolfram-alpha + config: + api_key: ${env.WOLFRAM_ALPHA_API_KEY:} +metadata_store: + type: sqlite + db_path: ${env.SQLITE_STORE_DIR:~/.llama/distributions/ollama}/registry.db +models: +- metadata: {} + model_id: ${env.INFERENCE_MODEL} + provider_id: custom_ollama + model_type: llm +- metadata: + embedding_dimension: 384 + model_id: all-MiniLM-L6-v2 + provider_id: custom_ollama + provider_model_id: all-minilm:latest + model_type: embedding +shields: [] +vector_dbs: [] +datasets: [] +scoring_fns: [] +benchmarks: [] +tool_groups: +- toolgroup_id: builtin::websearch + provider_id: tavily-search +- toolgroup_id: builtin::rag + provider_id: rag-runtime +- toolgroup_id: builtin::code_interpreter + provider_id: code-interpreter +- toolgroup_id: builtin::wolfram_alpha + provider_id: wolfram-alpha +server: + port: 8321 +external_providers_dir: /tmp/providers.d diff --git a/tests/unit/distribution/test_distribution.py b/tests/unit/distribution/test_distribution.py new file mode 100644 index 000000000..a4daffb82 --- /dev/null +++ b/tests/unit/distribution/test_distribution.py @@ -0,0 +1,223 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +from typing import Any, Dict +from unittest.mock import patch + +import pytest +import yaml +from pydantic import BaseModel, Field, ValidationError + +from llama_stack.distribution.datatypes import Api, Provider, StackRunConfig +from llama_stack.distribution.distribution import get_provider_registry +from llama_stack.providers.datatypes import ProviderSpec + + +class SampleConfig(BaseModel): + foo: str = Field( + default="bar", + description="foo", + ) + + @classmethod + def sample_run_config(cls, **kwargs: Any) -> Dict[str, Any]: + return { + "foo": "baz", + } + + +@pytest.fixture +def mock_providers(): + """Mock the available_providers function to return test providers.""" + with patch("llama_stack.providers.registry.inference.available_providers") as mock: + mock.return_value = [ + ProviderSpec( + provider_type="test_provider", + api=Api.inference, + adapter_type="test_adapter", + config_class="test_provider.config.TestProviderConfig", + ) + ] + yield mock + + +@pytest.fixture +def base_config(tmp_path): + """Create a base StackRunConfig with common settings.""" + return StackRunConfig( + image_name="test_image", + providers={ + "inference": [ + Provider( + provider_id="sample_provider", + provider_type="sample", + config=SampleConfig.sample_run_config(), + ) + ] + }, + external_providers_dir=str(tmp_path), + ) + + +@pytest.fixture +def provider_spec_yaml(): + """Common provider spec YAML for testing.""" + return """ +adapter: + adapter_type: test_provider + config_class: test_provider.config.TestProviderConfig + module: test_provider +api_dependencies: + - safety +""" + + +@pytest.fixture +def inline_provider_spec_yaml(): + """Common inline provider spec YAML for testing.""" + return """ +module: test_provider +config_class: test_provider.config.TestProviderConfig +pip_packages: + - test-package +api_dependencies: + - safety +optional_api_dependencies: + - vector_io +provider_data_validator: test_provider.validator.TestValidator +container_image: test-image:latest +""" + + +@pytest.fixture +def api_directories(tmp_path): + """Create the API directory structure for testing.""" + # Create remote provider directory + remote_inference_dir = tmp_path / "remote" / "inference" + remote_inference_dir.mkdir(parents=True, exist_ok=True) + + # Create inline provider directory + inline_inference_dir = tmp_path / "inline" / "inference" + inline_inference_dir.mkdir(parents=True, exist_ok=True) + + return remote_inference_dir, inline_inference_dir + + +class TestProviderRegistry: + """Test suite for provider registry functionality.""" + + def test_builtin_providers(self, mock_providers): + """Test loading built-in providers.""" + registry = get_provider_registry(None) + + assert Api.inference in registry + assert "test_provider" in registry[Api.inference] + assert registry[Api.inference]["test_provider"].provider_type == "test_provider" + assert registry[Api.inference]["test_provider"].api == Api.inference + + def test_external_remote_providers(self, api_directories, mock_providers, base_config, provider_spec_yaml): + """Test loading external remote providers from YAML files.""" + remote_dir, _ = api_directories + with open(remote_dir / "test_provider.yaml", "w") as f: + f.write(provider_spec_yaml) + + registry = get_provider_registry(base_config) + assert len(registry[Api.inference]) == 2 + + assert Api.inference in registry + assert "remote::test_provider" in registry[Api.inference] + provider = registry[Api.inference]["remote::test_provider"] + assert provider.adapter.adapter_type == "test_provider" + assert provider.adapter.module == "test_provider" + assert provider.adapter.config_class == "test_provider.config.TestProviderConfig" + assert Api.safety in provider.api_dependencies + + def test_external_inline_providers(self, api_directories, mock_providers, base_config, inline_provider_spec_yaml): + """Test loading external inline providers from YAML files.""" + _, inline_dir = api_directories + with open(inline_dir / "test_provider.yaml", "w") as f: + f.write(inline_provider_spec_yaml) + + registry = get_provider_registry(base_config) + assert len(registry[Api.inference]) == 2 + + assert Api.inference in registry + assert "inline::test_provider" in registry[Api.inference] + provider = registry[Api.inference]["inline::test_provider"] + assert provider.provider_type == "inline::test_provider" + assert provider.module == "test_provider" + assert provider.config_class == "test_provider.config.TestProviderConfig" + assert provider.pip_packages == ["test-package"] + assert Api.safety in provider.api_dependencies + assert Api.vector_io in provider.optional_api_dependencies + assert provider.provider_data_validator == "test_provider.validator.TestValidator" + assert provider.container_image == "test-image:latest" + + def test_invalid_yaml(self, api_directories, mock_providers, base_config): + """Test handling of invalid YAML files.""" + remote_dir, inline_dir = api_directories + with open(remote_dir / "invalid.yaml", "w") as f: + f.write("invalid: yaml: content: -") + with open(inline_dir / "invalid.yaml", "w") as f: + f.write("invalid: yaml: content: -") + + with pytest.raises(yaml.YAMLError): + get_provider_registry(base_config) + + def test_missing_directory(self, mock_providers): + """Test handling of missing external providers directory.""" + config = StackRunConfig( + image_name="test_image", + providers={ + "inference": [ + Provider( + provider_id="sample_provider", + provider_type="sample", + config=SampleConfig.sample_run_config(), + ) + ] + }, + external_providers_dir="/nonexistent/dir", + ) + with pytest.raises(FileNotFoundError): + get_provider_registry(config) + + def test_empty_api_directory(self, api_directories, mock_providers, base_config): + """Test handling of empty API directory.""" + registry = get_provider_registry(base_config) + assert len(registry[Api.inference]) == 1 # Only built-in provider + + def test_malformed_remote_provider_spec(self, api_directories, mock_providers, base_config): + """Test handling of malformed remote provider spec (missing required fields).""" + remote_dir, _ = api_directories + malformed_spec = """ +adapter: + adapter_type: test_provider + # Missing required fields +api_dependencies: + - safety +""" + with open(remote_dir / "malformed.yaml", "w") as f: + f.write(malformed_spec) + + with pytest.raises(ValidationError): + get_provider_registry(base_config) + + def test_malformed_inline_provider_spec(self, api_directories, mock_providers, base_config): + """Test handling of malformed inline provider spec (missing required fields).""" + _, inline_dir = api_directories + malformed_spec = """ +module: test_provider +# Missing required config_class +pip_packages: + - test-package +""" + with open(inline_dir / "malformed.yaml", "w") as f: + f.write(malformed_spec) + + with pytest.raises(KeyError) as exc_info: + get_provider_registry(base_config) + assert "config_class" in str(exc_info.value) From 3a9be58523254b7f471e636822c55963be41801e Mon Sep 17 00:00:00 2001 From: Matthew Farrellee Date: Wed, 9 Apr 2025 04:34:26 -0400 Subject: [PATCH 19/24] fix: use ollama list to find models (#1854) # What does this PR do? closes #1853 ## Test Plan ``` uv run llama stack build --image-type conda --image-name ollama --config llama_stack/templates/ollama/build.yaml ollama pull llama3.2:3b LLAMA_STACK_CONFIG=http://localhost:8321 uv run pytest tests/integration/inference/test_text_inference.py -v --text-model=llama3.2:3b ``` --- llama_stack/providers/remote/inference/ollama/ollama.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/llama_stack/providers/remote/inference/ollama/ollama.py b/llama_stack/providers/remote/inference/ollama/ollama.py index 5a78c07cc..12902996b 100644 --- a/llama_stack/providers/remote/inference/ollama/ollama.py +++ b/llama_stack/providers/remote/inference/ollama/ollama.py @@ -307,9 +307,10 @@ class OllamaInferenceAdapter(Inference, ModelsProtocolPrivate): if model.model_type == ModelType.embedding: logger.info(f"Pulling embedding model `{model.provider_resource_id}` if necessary...") await self.client.pull(model.provider_resource_id) - response = await self.client.list() - else: - response = await self.client.ps() + # we use list() here instead of ps() - + # - ps() only lists running models, not available models + # - models not currently running are run by the ollama server as needed + response = await self.client.list() available_models = [m["model"] for m in response["models"]] if model.provider_resource_id not in available_models: raise ValueError( From a2cf2999066aa583f6e356a6580862184916a998 Mon Sep 17 00:00:00 2001 From: Matthew Farrellee Date: Wed, 9 Apr 2025 04:35:19 -0400 Subject: [PATCH 20/24] fix: update getting started guide to use `ollama pull` (#1855) # What does this PR do? download the getting started w/ ollama model instead of downloading and running it. directly running it was necessary before https://github.com/meta-llama/llama-stack/pull/1854 ## Test Plan run the code on the page --- docs/source/getting_started/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/getting_started/index.md b/docs/source/getting_started/index.md index ef258a9cf..e9ad51961 100644 --- a/docs/source/getting_started/index.md +++ b/docs/source/getting_started/index.md @@ -6,13 +6,13 @@ Llama Stack is a stateful service with REST APIs to support seamless transition In this guide, we'll walk through how to build a RAG agent locally using Llama Stack with [Ollama](https://ollama.com/) to run inference on a Llama Model. -### 1. Start Ollama +### 1. Download a Llama model with Ollama ```bash -ollama run llama3.2:3b --keepalive 60m +ollama pull llama3.2:3b-instruct-fp16 ``` -By default, Ollama keeps the model loaded in memory for 5 minutes which can be too short. We set the `--keepalive` flag to 60 minutes to ensure the model remains loaded for sometime. +This will instruct the Ollama service to download the Llama 3.2 3B Instruct model, which we'll use in the rest of this guide. ```{admonition} Note :class: tip From 22814299b00ecd4fbd996d8a631aef6645818e6e Mon Sep 17 00:00:00 2001 From: Paolo Dettori Date: Wed, 9 Apr 2025 04:56:07 -0400 Subject: [PATCH 21/24] fix: solve unregister_toolgroup error (#1608) # What does this PR do? Fixes issue #1537 that causes "500 Internal Server Error" when unregistering a toolgroup # (Closes #1537 ) ## Test Plan ```console $ pytest -s -v tests/integration/tool_runtime/test_registration.py --stack-config=ollama --env INFERENCE_MODEL="meta-llama/Llama-3.2-3B-Instruct" INFO 2025-03-14 21:15:03,999 tests.integration.conftest:41 tests: Setting DISABLE_CODE_SANDBOX=1 for macOS /opt/homebrew/lib/python3.10/site-packages/pytest_asyncio/plugin.py:207: PytestDeprecationWarning: The configuration option "asyncio_default_fixture_loop_scope" is unset. The event loop scope for asynchronous fixtures will default to the fixture caching scope. Future versions of pytest-asyncio will default the loop scope for asynchronous fixtures to function scope. Set the default fixture loop scope explicitly in order to avoid unexpected behavior in the future. Valid fixture loop scopes are: "function", "class", "module", "package", "session" warnings.warn(PytestDeprecationWarning(_DEFAULT_FIXTURE_LOOP_SCOPE_UNSET)) ===================================================== test session starts ===================================================== platform darwin -- Python 3.10.16, pytest-8.3.5, pluggy-1.5.0 -- /opt/homebrew/opt/python@3.10/bin/python3.10 cachedir: .pytest_cache rootdir: /Users/paolo/Projects/aiplatform/llama-stack configfile: pyproject.toml plugins: asyncio-0.25.3, anyio-4.8.0 asyncio: mode=strict, asyncio_default_fixture_loop_scope=None collected 1 item tests/integration/tool_runtime/test_registration.py::test_register_and_unregister_toolgroup[None-None-None-None-None] INFO 2025-03-14 21:15:04,478 llama_stack.providers.remote.inference.ollama.ollama:75 inference: checking connectivity to Ollama at `http://localhost:11434`... INFO 2025-03-14 21:15:05,350 llama_stack.providers.remote.inference.ollama.ollama:294 inference: Pulling embedding model `all-minilm:latest` if necessary... INFO: Started server process [78391] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) INFO: 127.0.0.1:57424 - "GET /sse HTTP/1.1" 200 OK INFO: 127.0.0.1:57434 - "GET /sse HTTP/1.1" 200 OK INFO 2025-03-14 21:15:16,129 mcp.client.sse:51 uncategorized: Connecting to SSE endpoint: http://localhost:8000/sse INFO: 127.0.0.1:57445 - "GET /sse HTTP/1.1" 200 OK INFO 2025-03-14 21:15:16,146 mcp.client.sse:71 uncategorized: Received endpoint URL: http://localhost:8000/messages/?session_id=c5b6fc01f8dc4b5e80e38eb1c1b22a9b INFO 2025-03-14 21:15:16,147 mcp.client.sse:140 uncategorized: Starting post writer with endpoint URL: http://localhost:8000/messages/?session_id=c5b6fc01f8dc4b5e80e38eb1c1b22a9b INFO: 127.0.0.1:57447 - "POST /messages/?session_id=c5b6fc01f8dc4b5e80e38eb1c1b22a9b HTTP/1.1" 202 Accepted INFO: 127.0.0.1:57447 - "POST /messages/?session_id=c5b6fc01f8dc4b5e80e38eb1c1b22a9b HTTP/1.1" 202 Accepted INFO: 127.0.0.1:57447 - "POST /messages/?session_id=c5b6fc01f8dc4b5e80e38eb1c1b22a9b HTTP/1.1" 202 Accepted INFO 2025-03-14 21:15:16,155 mcp.server.lowlevel.server:535 uncategorized: Processing request of type ListToolsRequest PASSED =============================================== 1 passed, 4 warnings in 12.17s ================================================ ``` --------- Signed-off-by: Paolo Dettori --- .../distribution/routers/routing_tables.py | 4 +- .../tool_runtime/test_registration.py | 124 ++++++++++++++++++ 2 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 tests/integration/tool_runtime/test_registration.py diff --git a/llama_stack/distribution/routers/routing_tables.py b/llama_stack/distribution/routers/routing_tables.py index 557330df7..f6adae49d 100644 --- a/llama_stack/distribution/routers/routing_tables.py +++ b/llama_stack/distribution/routers/routing_tables.py @@ -608,8 +608,8 @@ class ToolGroupsRoutingTable(CommonRoutingTableImpl, ToolGroups): tool_group = await self.get_tool_group(toolgroup_id) if tool_group is None: raise ValueError(f"Tool group {toolgroup_id} not found") - tools = (await self.list_tools(toolgroup_id)).data - for tool in tools: + tools = await self.list_tools(toolgroup_id) + for tool in getattr(tools, "data", []): await self.unregister_object(tool) await self.unregister_object(tool_group) diff --git a/tests/integration/tool_runtime/test_registration.py b/tests/integration/tool_runtime/test_registration.py new file mode 100644 index 000000000..e04b56652 --- /dev/null +++ b/tests/integration/tool_runtime/test_registration.py @@ -0,0 +1,124 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +import socket +import threading +import time + +import httpx +import mcp.types as types +import pytest +import uvicorn +from llama_stack_client.types.shared_params.url import URL +from mcp.server.fastmcp import Context, FastMCP +from mcp.server.sse import SseServerTransport +from starlette.applications import Starlette +from starlette.routing import Mount, Route + + +@pytest.fixture(scope="module") +def mcp_server(): + server = FastMCP("FastMCP Test Server") + + @server.tool() + async def fetch(url: str, ctx: Context) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: + headers = {"User-Agent": "MCP Test Server (github.com/modelcontextprotocol/python-sdk)"} + async with httpx.AsyncClient(follow_redirects=True, headers=headers) as client: + response = await client.get(url) + response.raise_for_status() + return [types.TextContent(type="text", text=response.text)] + + sse = SseServerTransport("/messages/") + + async def handle_sse(request): + async with sse.connect_sse(request.scope, request.receive, request._send) as streams: + await server._mcp_server.run( + streams[0], + streams[1], + server._mcp_server.create_initialization_options(), + ) + + app = Starlette( + debug=True, + routes=[ + Route("/sse", endpoint=handle_sse), + Mount("/messages/", app=sse.handle_post_message), + ], + ) + + def get_open_port(): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.bind(("", 0)) + return sock.getsockname()[1] + + port = get_open_port() + + def run_server(): + uvicorn.run(app, host="0.0.0.0", port=port) + + # Start the server in a new thread + server_thread = threading.Thread(target=run_server, daemon=True) + server_thread.start() + + # Polling until the server is ready + timeout = 10 + start_time = time.time() + + while time.time() - start_time < timeout: + try: + response = httpx.get(f"http://localhost:{port}/sse") + if response.status_code == 200: + break + except (httpx.RequestError, httpx.HTTPStatusError): + pass + time.sleep(0.1) + + yield port + + +def test_register_and_unregister_toolgroup(llama_stack_client, mcp_server): + """ + Integration test for registering and unregistering a toolgroup using the ToolGroups API. + """ + port = mcp_server + test_toolgroup_id = "remote::web-fetch" + provider_id = "model-context-protocol" + + # Cleanup before running the test + toolgroups = llama_stack_client.toolgroups.list() + for toolgroup in toolgroups: + if toolgroup.identifier == test_toolgroup_id: + llama_stack_client.toolgroups.unregister(toolgroup_id=test_toolgroup_id) + + # Register the toolgroup + llama_stack_client.toolgroups.register( + toolgroup_id=test_toolgroup_id, + provider_id=provider_id, + mcp_endpoint=URL(uri=f"http://localhost:{port}/sse"), + ) + + # Verify registration + registered_toolgroup = llama_stack_client.toolgroups.get(toolgroup_id=test_toolgroup_id) + assert registered_toolgroup is not None + assert registered_toolgroup.identifier == test_toolgroup_id + assert registered_toolgroup.provider_id == provider_id + + # Verify tools listing + tools_list_response = llama_stack_client.tools.list(toolgroup_id=test_toolgroup_id) + assert isinstance(tools_list_response, list) + assert tools_list_response + + # Unregister the toolgroup + llama_stack_client.toolgroups.unregister(toolgroup_id=test_toolgroup_id) + + # Verify it is unregistered + with pytest.raises(ValueError, match=f"Tool group '{test_toolgroup_id}' not found"): + llama_stack_client.toolgroups.get(toolgroup_id=test_toolgroup_id) + + # Verify tools are also unregistered + unregister_tools_list_response = llama_stack_client.tools.list(toolgroup_id=test_toolgroup_id) + assert isinstance(unregister_tools_list_response, list) + assert not unregister_tools_list_response From 30b49d8dfa26fa0c07f39b6e7ce59d207fbcea82 Mon Sep 17 00:00:00 2001 From: Jaland Date: Wed, 9 Apr 2025 10:45:15 +0100 Subject: [PATCH 22/24] fix: Playground Container Issue (#1868) **What does this PR do?** This PR fixes a build issue with the Containerfile caused by missing requirement `llama-stack`. It updates the Containerfile to include the necessary requirements and upgrades the Python version to ensure successful builds. **Test Plan** The updated Containerfile has been tested, and the build now completes successfully with the required dependencies included. --- llama_stack/distribution/ui/Containerfile | 2 +- llama_stack/distribution/ui/requirements.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/llama_stack/distribution/ui/Containerfile b/llama_stack/distribution/ui/Containerfile index a97f25753..0126d1867 100644 --- a/llama_stack/distribution/ui/Containerfile +++ b/llama_stack/distribution/ui/Containerfile @@ -1,7 +1,7 @@ # More info on playground configuration can be found here: # https://llama-stack.readthedocs.io/en/latest/playground -FROM python:3.9-slim +FROM python:3.12-slim WORKDIR /app COPY . /app/ RUN /usr/local/bin/python -m pip install --upgrade pip && \ diff --git a/llama_stack/distribution/ui/requirements.txt b/llama_stack/distribution/ui/requirements.txt index 39f2b3d27..1e0456267 100644 --- a/llama_stack/distribution/ui/requirements.txt +++ b/llama_stack/distribution/ui/requirements.txt @@ -2,3 +2,4 @@ streamlit pandas llama-stack-client>=0.0.55 streamlit-option-menu +llama-stack>=0.1.9 From 96571053049e016b3509187ad3b00ce4fa86dc72 Mon Sep 17 00:00:00 2001 From: Michael Clifford Date: Wed, 9 Apr 2025 09:26:52 -0400 Subject: [PATCH 23/24] feat: Add tools page to playground (#1904) # What does this PR do? This PR adds an additional page to the playground called "Tools". This page connects to a llama-stack server and lists all the available LLM models, builtin tools and MCP tools in the sidebar. Users can select whatever combination of model and tools they want from the sidebar for their agent. Once the selections are made, users can chat with their agent similarly to the RAG page and test out agent tool use. closes #1902 ## Test Plan Ran the following commands with a llama-stack server and the updated playground worked as expected. ``` export LLAMA_STACK_ENDPOINT="http://localhost:8321" streamlit run llama_stack/distribution/ui/app.py ``` [//]: # (## Documentation) Signed-off-by: Michael Clifford --- llama_stack/distribution/ui/app.py | 2 + .../distribution/ui/page/playground/tools.py | 116 ++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 llama_stack/distribution/ui/page/playground/tools.py diff --git a/llama_stack/distribution/ui/app.py b/llama_stack/distribution/ui/app.py index 045b07982..441f65d20 100644 --- a/llama_stack/distribution/ui/app.py +++ b/llama_stack/distribution/ui/app.py @@ -24,6 +24,7 @@ def main(): # Playground pages chat_page = st.Page("page/playground/chat.py", title="Chat", icon="💬", default=True) rag_page = st.Page("page/playground/rag.py", title="RAG", icon="💬", default=False) + tool_page = st.Page("page/playground/tools.py", title="Tools", icon="🛠", default=False) # Distribution pages resources_page = st.Page("page/distribution/resources.py", title="Resources", icon="🔍", default=False) @@ -39,6 +40,7 @@ def main(): "Playground": [ chat_page, rag_page, + tool_page, application_evaluation_page, native_evaluation_page, ], diff --git a/llama_stack/distribution/ui/page/playground/tools.py b/llama_stack/distribution/ui/page/playground/tools.py new file mode 100644 index 000000000..e987f617b --- /dev/null +++ b/llama_stack/distribution/ui/page/playground/tools.py @@ -0,0 +1,116 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the terms described in the LICENSE file in +# the root directory of this source tree. + +import uuid + +import streamlit as st +from llama_stack_client import Agent + +from llama_stack.distribution.ui.modules.api import llama_stack_api + + +def tool_chat_page(): + st.title("🛠 Tools") + + client = llama_stack_api.client + models = client.models.list() + model_list = [model.identifier for model in models if model.api_model_type == "llm"] + + tool_groups = client.toolgroups.list() + tool_groups_list = [tool_group.identifier for tool_group in tool_groups] + mcp_tools_list = [tool for tool in tool_groups_list if tool.startswith("mcp::")] + builtin_tools_list = [tool for tool in tool_groups_list if not tool.startswith("mcp::")] + + def reset_agent(): + st.session_state.clear() + st.cache_resource.clear() + + with st.sidebar: + st.subheader("Model") + model = st.selectbox(label="models", options=model_list, on_change=reset_agent) + + st.subheader("Builtin Tools") + toolgroup_selection = st.pills( + label="Available ToolGroups", options=builtin_tools_list, selection_mode="multi", on_change=reset_agent + ) + + st.subheader("MCP Servers") + mcp_selection = st.pills( + label="Available MCP Servers", options=mcp_tools_list, selection_mode="multi", on_change=reset_agent + ) + + toolgroup_selection.extend(mcp_selection) + + active_tool_list = [] + for toolgroup_id in toolgroup_selection: + active_tool_list.extend( + [ + f"{''.join(toolgroup_id.split('::')[1:])}:{t.identifier}" + for t in client.tools.list(toolgroup_id=toolgroup_id) + ] + ) + + st.subheader(f"Active Tools: 🛠 {len(active_tool_list)}") + st.json(active_tool_list) + + @st.cache_resource + def create_agent(): + return Agent( + client, + model=model, + instructions="You are a helpful assistant. When you use a tool always respond with a summary of the result.", + tools=toolgroup_selection, + sampling_params={ + "strategy": {"type": "greedy"}, + }, + ) + + agent = create_agent() + + if "agent_session_id" not in st.session_state: + st.session_state["agent_session_id"] = agent.create_session(session_name=f"tool_demo_{uuid.uuid4()}") + + session_id = st.session_state["agent_session_id"] + + if "messages" not in st.session_state: + st.session_state["messages"] = [{"role": "assistant", "content": "How can I help you?"}] + + for msg in st.session_state.messages: + with st.chat_message(msg["role"]): + st.markdown(msg["content"]) + + if prompt := st.chat_input(placeholder=""): + with st.chat_message("user"): + st.markdown(prompt) + + st.session_state.messages.append({"role": "user", "content": prompt}) + + turn_response = agent.create_turn( + session_id=session_id, + messages=[{"role": "user", "content": prompt}], + stream=True, + ) + + def response_generator(turn_response): + for response in turn_response: + if hasattr(response.event, "payload"): + print(response.event.payload) + if response.event.payload.event_type == "step_progress": + if hasattr(response.event.payload.delta, "text"): + yield response.event.payload.delta.text + if response.event.payload.event_type == "step_complete": + if response.event.payload.step_details.step_type == "tool_execution": + yield " 🛠 " + else: + yield f"Error occurred in the Llama Stack Cluster: {response}" + + with st.chat_message("assistant"): + response = st.write_stream(response_generator(turn_response)) + + st.session_state.messages.append({"role": "assistant", "content": response}) + + +tool_chat_page() From 692f56068c24c42fe4a5543aec04a3f7f9bd3925 Mon Sep 17 00:00:00 2001 From: Yuan Tang Date: Wed, 9 Apr 2025 09:34:41 -0400 Subject: [PATCH 24/24] docs: Add recent release notes (#1899) # What does this PR do? These are missing and changelog doc automation is not working yet due to missing permissions for GitHub Actions: https://dev.to/suzuki0430/how-to-enable-the-allow-github-actions-to-create-and-approve-pull-requests-option-when-its-grayed-out-3e1i --------- Signed-off-by: Yuan Tang --- CHANGELOG.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 953d04def..5086094ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,42 @@ # Changelog +# v0.2.1 +Published on: 2025-04-05T23:13:00Z + + + +--- + +# v0.2.0 +Published on: 2025-04-05T19:04:29Z + +## Llama 4 Support + +Checkout more at https://www.llama.com + + + +--- + +# v0.1.9 +Published on: 2025-03-29T00:52:23Z + +### Build and Test Agents +* Agents: Entire document context with attachments +* RAG: Documentation with sqlite-vec faiss comparison +* Getting started: Fixes to getting started notebook. + +### Agent Evals and Model Customization +* (**New**) Post-training: Add nemo customizer + +### Better Engineering +* Moved sqlite-vec to non-blocking calls +* Don't return a payload on file delete + + + +--- + # v0.1.8 Published on: 2025-03-24T01:28:50Z