Fireworks AI Testing: Serverless Inference, Compound AI, and Function Calling

Fireworks AI Testing: Serverless Inference, Compound AI, and Function Calling

Fireworks AI differentiates itself with developer experience: fastest cold start times in the serverless LLM market, a compound AI system builder (FireFunction), and transparent pricing. Testing Fireworks AI integrations means validating not just completion quality but the specific behaviors of their serverless infrastructure and FireFunction orchestration layer.

This guide covers comprehensive testing for Fireworks AI applications.

What Makes Fireworks AI Testing Specific

Fireworks AI has unique characteristics that shape your test strategy:

  • Serverless with fast cold starts: Cold start latency is ~300ms vs 3-5s for other providers. Tests can measure this.
  • FireFunction models: firefunction-v2 is fine-tuned specifically for function calling with better instruction following
  • Model versioning: Models are versioned with explicit IDs like accounts/fireworks/models/llama-v3p1-8b-instruct
  • OpenAI-compatible API: The API is fully OpenAI-compatible, so the openai SDK works
  • Compound AI: FireAttention and FireOptimizer change serving behavior

Setting Up the Test Environment

Fireworks AI's OpenAI-compatible API means you can use the openai SDK or fireworks-ai:

pip install fireworks-ai openai pytest pytest-asyncio httpx

conftest.py:

import pytest
from unittest.mock import MagicMock, patch
import fireworks.client as fw_client
from openai import OpenAI  # Also works with Fireworks

FIREWORKS_BASE_URL = "https://api.fireworks.ai/inference/v1"

# Fireworks AI model identifiers
FIREWORKS_MODELS = {
    "llama-3-1-8b": "accounts/fireworks/models/llama-v3p1-8b-instruct",
    "llama-3-1-70b": "accounts/fireworks/models/llama-v3p1-70b-instruct",
    "mixtral-8x22b": "accounts/fireworks/models/mixtral-8x22b-instruct",
    "firefunction-v2": "accounts/fireworks/models/firefunction-v2",
    "qwen2-72b": "accounts/fireworks/models/qwen2-72b-instruct",
}

@pytest.fixture
def openai_fireworks_client():
    """OpenAI SDK configured for Fireworks AI."""
    return OpenAI(
        api_key="fw_test_key_not_real",
        base_url=FIREWORKS_BASE_URL
    )

@pytest.fixture
def mock_completion():
    """Standard mock completion response."""
    response = MagicMock()
    response.id = "chatcmpl-fireworks-abc123"
    response.model = "accounts/fireworks/models/llama-v3p1-8b-instruct"
    response.choices = [MagicMock()]
    response.choices[0].index = 0
    response.choices[0].message.role = "assistant"
    response.choices[0].message.content = "Test response"
    response.choices[0].message.tool_calls = None
    response.choices[0].finish_reason = "stop"
    response.usage = MagicMock()
    response.usage.prompt_tokens = 12
    response.usage.completion_tokens = 18
    response.usage.total_tokens = 30
    return response

@pytest.fixture
def function_call_response():
    """Mock response with function call."""
    tool_call = MagicMock()
    tool_call.id = "call_fw_abc123"
    tool_call.type = "function"
    tool_call.function.name = "search_products"
    tool_call.function.arguments = '{"query": "blue running shoes", "max_price": 150}'
    
    response = MagicMock()
    response.choices = [MagicMock()]
    response.choices[0].message.role = "assistant"
    response.choices[0].message.content = None
    response.choices[0].message.tool_calls = [tool_call]
    response.choices[0].finish_reason = "tool_calls"
    response.usage = MagicMock(prompt_tokens=80, completion_tokens=40, total_tokens=120)
    return response

Testing Basic Serverless Inference

def test_basic_completion(openai_fireworks_client, mock_completion):
    """Basic completion via Fireworks-compatible API."""
    with patch.object(openai_fireworks_client.chat.completions, "create",
                      return_value=mock_completion):
        response = openai_fireworks_client.chat.completions.create(
            model="accounts/fireworks/models/llama-v3p1-8b-instruct",
            messages=[{"role": "user", "content": "Hello"}]
        )
        
        assert response.choices[0].message.content == "Test response"
        assert response.usage.total_tokens == 30

def test_fireworks_model_id_format():
    """Fireworks model IDs should follow accounts/fireworks/models/ convention."""
    import re
    
    fireworks_model_pattern = re.compile(
        r'^accounts/[a-z0-9-]+/models/[a-z0-9._-]+$'
    )
    
    valid_models = [
        "accounts/fireworks/models/llama-v3p1-8b-instruct",
        "accounts/fireworks/models/firefunction-v2",
        "accounts/myorg/models/my-custom-model",
    ]
    
    invalid_models = [
        "llama-v3p1-8b-instruct",  # Missing prefix
        "accounts/fireworks/llama",  # Missing 'models/'
        "",
    ]
    
    for model in valid_models:
        assert fireworks_model_pattern.match(model), f"Should be valid: {model}"
    
    for model in invalid_models:
        assert not fireworks_model_pattern.match(model), f"Should be invalid: {model}"

def test_context_length_by_model():
    """Different Fireworks models have different context limits."""
    model_context_limits = {
        "accounts/fireworks/models/llama-v3p1-8b-instruct": 131072,
        "accounts/fireworks/models/llama-v3p1-70b-instruct": 131072,
        "accounts/fireworks/models/mixtral-8x22b-instruct": 65536,
        "accounts/fireworks/models/firefunction-v2": 8192,
    }
    
    def get_context_limit(model_id: str) -> int:
        return model_context_limits.get(model_id, 8192)  # Default fallback
    
    assert get_context_limit("accounts/fireworks/models/llama-v3p1-8b-instruct") == 131072
    assert get_context_limit("accounts/fireworks/models/firefunction-v2") == 8192
    assert get_context_limit("unknown/model") == 8192  # Fallback

def test_prompt_truncation_for_context_limit():
    """Messages exceeding context limit should be handled gracefully."""
    def estimate_tokens(text: str) -> int:
        """Rough token estimate (4 chars per token)."""
        return len(text) // 4
    
    def truncate_to_context(messages: list, model: str, max_tokens: int = 8192) -> list:
        """Truncate message history to fit context window."""
        total_tokens = sum(estimate_tokens(m.get("content", "")) for m in messages)
        
        while total_tokens > max_tokens and len(messages) > 1:
            # Remove oldest non-system message
            for i, msg in enumerate(messages):
                if msg["role"] != "system":
                    total_tokens -= estimate_tokens(msg.get("content", ""))
                    messages.pop(i)
                    break
        
        return messages
    
    # Build a long conversation
    messages = [{"role": "system", "content": "You are helpful."}]
    for i in range(100):
        messages.append({"role": "user", "content": f"Message {i}: " + "word " * 100})
        messages.append({"role": "assistant", "content": f"Response {i}"})
    
    truncated = truncate_to_context(messages, "firefunction-v2", max_tokens=2000)
    
    # Should have fewer messages but still have system prompt
    assert len(truncated) < 200
    assert truncated[0]["role"] == "system"

Testing FireFunction for Function Calling

FireFunction-v2 is Fireworks' specialized function calling model:

import json

def test_firefunction_tool_definition(openai_fireworks_client):
    """FireFunction tool definitions follow OpenAI format."""
    tools = [
        {
            "type": "function",
            "function": {
                "name": "search_products",
                "description": "Search for products in the catalog",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "query": {
                            "type": "string",
                            "description": "Search query"
                        },
                        "max_price": {
                            "type": "number",
                            "description": "Maximum price in USD"
                        },
                        "category": {
                            "type": "string",
                            "enum": ["shoes", "clothing", "accessories"]
                        }
                    },
                    "required": ["query"]
                }
            }
        }
    ]
    
    with patch.object(openai_fireworks_client.chat.completions, "create") as mock:
        mock.return_value = MagicMock(
            choices=[MagicMock(
                message=MagicMock(content=None, tool_calls=[MagicMock()]),
                finish_reason="tool_calls"
            )],
            usage=MagicMock()
        )
        
        openai_fireworks_client.chat.completions.create(
            model="accounts/fireworks/models/firefunction-v2",
            messages=[{"role": "user", "content": "Find blue running shoes under $150"}],
            tools=tools
        )
        
        call_kwargs = mock.call_args.kwargs
        assert call_kwargs["tools"][0]["type"] == "function"
        assert call_kwargs["tools"][0]["function"]["name"] == "search_products"

def test_firefunction_response_parsing(openai_fireworks_client, function_call_response):
    """Function call responses should be correctly parsed."""
    with patch.object(openai_fireworks_client.chat.completions, "create",
                      return_value=function_call_response):
        response = openai_fireworks_client.chat.completions.create(
            model="accounts/fireworks/models/firefunction-v2",
            messages=[{"role": "user", "content": "Find blue running shoes"}],
            tools=[{"type": "function", "function": {"name": "search_products"}}]
        )
        
        assert response.choices[0].finish_reason == "tool_calls"
        
        tool_call = response.choices[0].message.tool_calls[0]
        assert tool_call.function.name == "search_products"
        
        args = json.loads(tool_call.function.arguments)
        assert "query" in args
        assert "blue running shoes" in args["query"].lower()

def test_parallel_function_calls(openai_fireworks_client):
    """FireFunction can call multiple functions in parallel."""
    tool_call_1 = MagicMock()
    tool_call_1.id = "call_001"
    tool_call_1.function.name = "get_weather"
    tool_call_1.function.arguments = '{"city": "Paris"}'
    
    tool_call_2 = MagicMock()
    tool_call_2.id = "call_002"
    tool_call_2.function.name = "get_exchange_rate"
    tool_call_2.function.arguments = '{"from": "USD", "to": "EUR"}'
    
    parallel_response = MagicMock()
    parallel_response.choices = [MagicMock()]
    parallel_response.choices[0].message.tool_calls = [tool_call_1, tool_call_2]
    parallel_response.choices[0].finish_reason = "tool_calls"
    
    with patch.object(openai_fireworks_client.chat.completions, "create",
                      return_value=parallel_response):
        response = openai_fireworks_client.chat.completions.create(
            model="accounts/fireworks/models/firefunction-v2",
            messages=[{
                "role": "user",
                "content": "What's the weather in Paris and current USD/EUR exchange rate?"
            }],
            tools=[
                {"type": "function", "function": {"name": "get_weather"}},
                {"type": "function", "function": {"name": "get_exchange_rate"}}
            ]
        )
        
        tool_calls = response.choices[0].message.tool_calls
        assert len(tool_calls) == 2
        
        function_names = {tc.function.name for tc in tool_calls}
        assert "get_weather" in function_names
        assert "get_exchange_rate" in function_names

def test_firefunction_structured_output(openai_fireworks_client):
    """FireFunction should produce schema-conforming JSON output."""
    from jsonschema import validate
    
    output_schema = {
        "type": "object",
        "properties": {
            "products": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "name": {"type": "string"},
                        "price": {"type": "number"},
                        "in_stock": {"type": "boolean"}
                    },
                    "required": ["name", "price", "in_stock"]
                }
            },
            "total_results": {"type": "integer"}
        },
        "required": ["products", "total_results"]
    }
    
    mock_json_output = {
        "products": [
            {"name": "Nike Air Zoom", "price": 129.99, "in_stock": True},
            {"name": "Adidas Ultraboost", "price": 149.99, "in_stock": False}
        ],
        "total_results": 2
    }
    
    mock_response = MagicMock()
    mock_response.choices = [MagicMock()]
    mock_response.choices[0].message.content = json.dumps(mock_json_output)
    mock_response.choices[0].finish_reason = "stop"
    
    with patch.object(openai_fireworks_client.chat.completions, "create",
                      return_value=mock_response):
        response = openai_fireworks_client.chat.completions.create(
            model="accounts/fireworks/models/firefunction-v2",
            messages=[{"role": "user", "content": "Search products"}],
            response_format={"type": "json_object"}
        )
        
        result = json.loads(response.choices[0].message.content)
        validate(instance=result, schema=output_schema)  # Raises if invalid
        
        assert len(result["products"]) == 2
        assert result["total_results"] == 2

Testing Compound AI Systems

Fireworks AI is designed to orchestrate multiple models in compound AI workflows:

import asyncio
from dataclasses import dataclass
from typing import Optional

@dataclass
class AgentStep:
    """Represents one step in a compound AI workflow."""
    model: str
    input: str
    output: Optional[str] = None
    tool_calls: Optional[list] = None
    error: Optional[str] = None

class CompoundAIWorkflow:
    """Multi-step AI workflow using Fireworks AI."""
    
    def __init__(self, client):
        self.client = client
        self.steps: list[AgentStep] = []
    
    def run_step(self, model: str, messages: list, tools=None) -> dict:
        kwargs = {"model": model, "messages": messages}
        if tools:
            kwargs["tools"] = tools
        
        response = self.client.chat.completions.create(**kwargs)
        
        step = AgentStep(model=model, input=messages[-1]["content"])
        
        if response.choices[0].message.tool_calls:
            step.tool_calls = response.choices[0].message.tool_calls
        else:
            step.output = response.choices[0].message.content
        
        self.steps.append(step)
        return response

def test_compound_workflow_step_tracking(openai_fireworks_client):
    """Compound AI workflows should track each step."""
    workflow = CompoundAIWorkflow(openai_fireworks_client)
    
    step_responses = []
    for i in range(3):
        mock_r = MagicMock()
        mock_r.choices = [MagicMock()]
        mock_r.choices[0].message.content = f"Step {i+1} output"
        mock_r.choices[0].message.tool_calls = None
        mock_r.choices[0].finish_reason = "stop"
        step_responses.append(mock_r)
    
    with patch.object(openai_fireworks_client.chat.completions, "create",
                      side_effect=step_responses):
        # Step 1: Extract intent
        workflow.run_step(
            model="accounts/fireworks/models/llama-v3p1-8b-instruct",
            messages=[{"role": "user", "content": "Book a flight to Paris"}]
        )
        
        # Step 2: Plan actions
        workflow.run_step(
            model="accounts/fireworks/models/firefunction-v2",
            messages=[{"role": "user", "content": "What tools do I need?"}]
        )
        
        # Step 3: Synthesize
        workflow.run_step(
            model="accounts/fireworks/models/llama-v3p1-70b-instruct",
            messages=[{"role": "user", "content": "Create the booking summary"}]
        )
    
    assert len(workflow.steps) == 3
    assert workflow.steps[0].model == "accounts/fireworks/models/llama-v3p1-8b-instruct"
    assert workflow.steps[2].model == "accounts/fireworks/models/llama-v3p1-70b-instruct"
    assert all(step.error is None for step in workflow.steps)

def test_compound_workflow_handles_partial_failure(openai_fireworks_client):
    """Workflow should handle step failures gracefully."""
    from openai import APIError
    
    success_response = MagicMock()
    success_response.choices = [MagicMock()]
    success_response.choices[0].message.content = "Success"
    success_response.choices[0].message.tool_calls = None
    
    failure = APIError("Model overloaded", response=MagicMock(status_code=503), body={})
    
    responses = [success_response, failure, success_response]
    call_count = [0]
    
    def side_effects(**kwargs):
        idx = call_count[0]
        call_count[0] += 1
        if isinstance(responses[idx], Exception):
            raise responses[idx]
        return responses[idx]
    
    results = []
    errors = []
    
    with patch.object(openai_fireworks_client.chat.completions, "create",
                      side_effect=side_effects):
        for i in range(3):
            try:
                r = openai_fireworks_client.chat.completions.create(
                    model="accounts/fireworks/models/llama-v3p1-8b-instruct",
                    messages=[{"role": "user", "content": f"Step {i}"}]
                )
                results.append(r)
            except APIError as e:
                errors.append(str(e))
    
    assert len(results) == 2
    assert len(errors) == 1
    assert "503" in errors[0] or "overloaded" in errors[0].lower()

Testing Streaming with Fireworks AI

def make_fireworks_stream(tokens: list[str]):
    """Create mock Fireworks streaming response."""
    for i, token in enumerate(tokens):
        chunk = MagicMock()
        chunk.id = "stream-fw-test"
        chunk.model = "accounts/fireworks/models/llama-v3p1-8b-instruct"
        chunk.choices = [MagicMock()]
        chunk.choices[0].delta.content = token
        chunk.choices[0].delta.role = "assistant" if i == 0 else None
        chunk.choices[0].finish_reason = "stop" if i == len(tokens) - 1 else None
        yield chunk

def test_streaming_response(openai_fireworks_client):
    """Streaming should yield tokens in order."""
    tokens = ["Fireworks", " AI", " is", " fast", "!"]
    
    with patch.object(openai_fireworks_client.chat.completions, "create",
                      return_value=make_fireworks_stream(tokens)):
        stream = openai_fireworks_client.chat.completions.create(
            model="accounts/fireworks/models/llama-v3p1-8b-instruct",
            messages=[{"role": "user", "content": "Tell me about Fireworks AI"}],
            stream=True
        )
        
        full_text = ""
        for chunk in stream:
            if chunk.choices[0].delta.content:
                full_text += chunk.choices[0].delta.content
        
        assert full_text == "Fireworks AI is fast!"

def test_streaming_latency_tracking(openai_fireworks_client):
    """Track time-to-first-token and total generation time."""
    import time
    
    def delayed_stream():
        time.sleep(0.05)  # Cold start simulation
        yield from make_fireworks_stream(["Token1", " Token2", " Token3"])
    
    with patch.object(openai_fireworks_client.chat.completions, "create",
                      return_value=delayed_stream()):
        start = time.monotonic()
        
        stream = openai_fireworks_client.chat.completions.create(
            model="accounts/fireworks/models/llama-v3p1-8b-instruct",
            messages=[{"role": "user", "content": "test"}],
            stream=True
        )
        
        first_token_time = None
        for chunk in stream:
            if chunk.choices[0].delta.content and first_token_time is None:
                first_token_time = time.monotonic() - start
        
        total_time = time.monotonic() - start
        
        assert first_token_time is not None
        assert total_time >= first_token_time
        # Production assertion: assert first_token_time < YOUR_SLA

Error Handling

from openai import AuthenticationError, RateLimitError, APIStatusError

def test_invalid_api_key_raises(openai_fireworks_client):
    """Bad API key should raise AuthenticationError."""
    with patch.object(openai_fireworks_client.chat.completions, "create",
                      side_effect=AuthenticationError(
                          message="Invalid API key",
                          response=MagicMock(status_code=401),
                          body={}
                      )):
        with pytest.raises(AuthenticationError):
            openai_fireworks_client.chat.completions.create(
                model="accounts/fireworks/models/llama-v3p1-8b-instruct",
                messages=[{"role": "user", "content": "test"}]
            )

def test_model_overloaded_503(openai_fireworks_client):
    """503 errors should trigger fallback to smaller model."""
    overloaded = APIStatusError(
        message="Model overloaded",
        response=MagicMock(status_code=503),
        body={"error": {"message": "Model overloaded, try again later"}}
    )
    
    fallback_response = MagicMock()
    fallback_response.choices = [MagicMock()]
    fallback_response.choices[0].message.content = "Fallback response"
    fallback_response.choices[0].message.tool_calls = None
    
    def fallback_completion(model, messages, **kwargs):
        if "70b" in model:
            raise overloaded
        return fallback_response
    
    with patch.object(openai_fireworks_client.chat.completions, "create",
                      side_effect=lambda **kwargs: fallback_completion(**kwargs)):
        def with_model_fallback(client, messages, preferred, fallback):
            try:
                return client.chat.completions.create(
                    model=preferred,
                    messages=messages
                )
            except APIStatusError as e:
                if e.response.status_code == 503:
                    return client.chat.completions.create(
                        model=fallback,
                        messages=messages
                    )
                raise
        
        result = with_model_fallback(
            openai_fireworks_client,
            messages=[{"role": "user", "content": "test"}],
            preferred="accounts/fireworks/models/llama-v3p1-70b-instruct",
            fallback="accounts/fireworks/models/llama-v3p1-8b-instruct"
        )
        
        assert result.choices[0].message.content == "Fallback response"

CI/CD Configuration

name: Fireworks AI Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      
      - name: Install
        run: pip install fireworks-ai openai pytest pytest-asyncio pytest-cov jsonschema
      
      - name: Unit tests (mocked)
        run: pytest tests/ -v -m "not integration" --cov=src
        env:
          FIREWORKS_API_KEY: "fw_test_mock"
      
      - name: Model ID validation
        run: |
          python -c "
          import re
          pattern = re.compile(r'^accounts/[a-z0-9-]+/models/[a-z0-9._-]+$')
          
          with open('config/models.txt') as f:
              for line in f:
                  model_id = line.strip()
                  if model_id and not model_id.startswith('#'):
                      assert pattern.match(model_id), f'Invalid model ID: {model_id}'
          
          print('All model IDs valid')
          "
      
      - name: Integration tests (main only)
        if: github.ref == 'refs/heads/main'
        run: pytest tests/ -m integration -v
        env:
          FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}

Monitoring Fireworks AI in Production

Serverless cold starts are real. Compound AI workflows have multiple failure points. FireFunction's tool calling behavior can change between model versions.

HelpMeTest monitors your Fireworks AI integration end-to-end on a 5-minute cadence. You write the test once — "call our search API, verify the product list returns in under 3 seconds with at least 5 results" — and HelpMeTest runs it continuously, alerting you when Fireworks cold starts creep past your SLA or when FireFunction starts returning malformed JSON.

The free tier includes 24/7 monitoring at 5-minute intervals with email alerts, which is enough to catch most production issues before users report them.

Summary

Fireworks AI testing has four areas to cover:

  1. Serverless inference — model ID format validation, context limits, completion correctness
  2. FireFunction calling — tool definitions, response parsing, parallel calls, structured JSON output
  3. Compound AI — step tracking, partial failure handling, model cascade patterns
  4. Operational — 503 fallback, cold start latency measurement, streaming token collection

The OpenAI SDK compatibility is a major advantage — your existing OpenAI test infrastructure works for Fireworks with only the base_url and model IDs changed. Focus your Fireworks-specific test effort on compound AI orchestration and serverless-specific latency behaviors.

Read more

Start now free