mirror of
https://github.com/tiennm99/litellm.git
synced 2026-07-08 21:03:08 +00:00
98a9005c76
* [Feat] Day-0 support for GPT-5.5 and GPT-5.5 Pro (#26449) * feat(openai): day-0 support for GPT-5.5 and GPT-5.5 Pro Add pricing + capability entries for the new GPT-5.5 family launched by OpenAI on 2026-04-24: - gpt-5.5 / gpt-5.5-2026-04-23 (chat): $5/$30/$0.50 per 1M input/output/cached input - gpt-5.5-pro / gpt-5.5-pro-2026-04-23 (responses-only): $60/$360/$6 per 1M input/output/cached input Other fees (long-context >272k, flex, batches, priority, cache discounts) follow the same ratios as GPT-5.4, with context window retained at 1.05M input / 128K output. No transformation / classifier code changes are required: OpenAIGPT5Config.is_model_gpt_5_4_plus_model() already matches 5.5+ via numeric version parsing, and model registration is driven from the JSON. The existing responses-API bridge for tools + reasoning_effort (litellm/main.py:970) already covers gpt-5.5-pro. Tests: - GPT5_MODELS regression list now covers gpt-5.5-pro and dated variants - New test_generic_cost_per_token_gpt55_pro cost-calc test - Updated test_generic_cost_per_token_gpt55 for long-context fields * fix(openai): mirror reasoning_effort flags onto gpt-5.5 dated variants gpt-5.5-2026-04-23 and gpt-5.5-pro-2026-04-23 were missing the supports_none_reasoning_effort, supports_xhigh_reasoning_effort, and supports_minimal_reasoning_effort flags that their non-dated counterparts define. Reasoning-effort routing in OpenAIGPT5Config is fully capability-driven from these JSON flags — since an absent flag is treated as False for opt-in levels (xhigh), users pinning to a dated snapshot would silently lose xhigh support and diverge from the base alias on logprobs + flexible temperature handling. Copy the flags onto both dated variants so every dated snapshot inherits the base model's reasoning-effort capability profile. Adds a parametrized regression test that asserts supports_{none,minimal,xhigh}_reasoning_effort parity between each dated variant and its non-dated counterpart, preventing future drift when new snapshots are added. * [Feat] Add azure/gpt-5.5 + azure/gpt-5.5-pro entries (+ dated variants) (#26361) * feat(azure): add azure/gpt-5.5 + azure/gpt-5.5-pro entries (+ dated variants) Azure variants of OpenAI's GPT-5.5 family. Microsoft has not yet shipped GPT-5.5 on Azure OpenAI (latest GA on the Foundry models page is GPT-5.4 as of 2026-04-24), but adding the entries day-0 mirrors the established precedent for azure/gpt-5.4* (which were in the cost map before the Azure rollout) so cost tracking and capability flags work the moment customers deploy. Schema follows the existing azure/gpt-5.4* shape: - Same base/long-context pricing as openai/gpt-5.5*: $5/$30 chat, $60/$360 pro per 1M, with priority tier 2x base - Azure variants drop the flex/batches keys (Azure has no flex tier) but keep priority pricing, matching gpt-5.4* precedent - mode=chat for the thinking model, mode=responses for pro reasoning_effort capability flags mirror the OpenAI variants exactly since Azure proxies the same API contract: minimal rejection on both chat and pro, low/none rejection on pro. Once #26456 (which sets supports_low_reasoning_effort + minimal=false on openai/gpt-5.5*) lands, OpenAI and Azure flag profiles align. Tests pin entry presence + pricing for all four Azure variants and verify the live-API-derived reasoning_effort flags. * test: register supports_low_reasoning_effort in cost-map JSON schema azure/gpt-5.5-pro and azure/gpt-5.5-pro-2026-04-23 added in this branch carry supports_low_reasoning_effort=false. The strict 'additionalProperties: false' schema in test_aaamodel_prices_and_context_window_json_is_valid rejected the new key. Register it alongside the other supports_*_reasoning_effort entries. Note: the runtime side of this flag (code that reads it) lands in #26456. Until that PR merges the flag is inert for both Azure and OpenAI pro entries, but having the schema accept it lets cost-map tests pass on either merge order. * fix(arize/langfuse_otel): handle Pydantic usage objects without `.get` `_set_usage_outputs` called `usage.get(...)` and `usage.get('output_tokens_details', {}).get('reasoning_tokens')`. These crash with `AttributeError: 'CompletionUsage' object has no attribute 'get'` when `usage` (or the nested token-details object) is a raw OpenAI Pydantic model rather than a dict / litellm `Usage` wrapper. Reproduces on the langfuse_otel + arize Responses API logging paths. Fixes #13672. Changes: - Add `_safe_get(obj, key, default)` that prefers dict-style `.get` when available and otherwise falls back to `getattr`. Works uniformly for dicts, litellm's `Usage`, and plain Pydantic models like `openai.types.completion_usage.CompletionUsage` / `CompletionTokensDetails` / `OutputTokensDetails`. - Use `_safe_get` for total / completion / prompt / output tokens. - Look for reasoning tokens in `completion_tokens_details` (Chat Completions API) before falling back to `output_tokens_details` (Responses API). Previously reasoning tokens from the Chat Completions API were silently dropped. Tests: - `test_set_usage_outputs_pydantic_completion_usage` — covers the chat completions path with raw `CompletionUsage` + `CompletionTokensDetails`. - `test_set_usage_outputs_pydantic_response_api_usage` — covers the Responses API path with a Pydantic usage object lacking `.get`. Both tests fail on main before this commit and pass after. --------- Co-authored-by: yuneng-jiang <yuneng@berri.ai> Co-authored-by: Mateo Wang <277851410+mateo-berri@users.noreply.github.com> Co-authored-by: alvinttang <alvin@pm.me> Co-authored-by: Krrish Dholakia <krrish+github@berri.ai>
454 lines
16 KiB
Python
454 lines
16 KiB
Python
import json
|
|
import os
|
|
import sys
|
|
from typing import Optional
|
|
|
|
# Adds the grandparent directory to sys.path to allow importing project modules
|
|
sys.path.insert(0, os.path.abspath("../.."))
|
|
|
|
import asyncio
|
|
|
|
import pytest
|
|
|
|
import litellm
|
|
from litellm.integrations._types.open_inference import (
|
|
MessageAttributes,
|
|
SpanAttributes,
|
|
ToolCallAttributes,
|
|
)
|
|
from litellm.integrations.arize.arize import ArizeLogger
|
|
from litellm.integrations.custom_logger import CustomLogger
|
|
from litellm.types.utils import Choices, StandardCallbackDynamicParams
|
|
|
|
|
|
def test_arize_set_attributes():
|
|
"""
|
|
Test setting attributes for Arize, including all custom LLM attributes.
|
|
Ensures that the correct span attributes are being added during a request.
|
|
"""
|
|
from unittest.mock import MagicMock
|
|
|
|
from litellm.types.utils import ModelResponse
|
|
|
|
span = MagicMock() # Mocked tracing span to test attribute setting
|
|
|
|
# Construct kwargs to simulate a real LLM request scenario
|
|
kwargs = {
|
|
"model": "gpt-4o",
|
|
"messages": [{"role": "user", "content": "Basic Request Content"}],
|
|
"standard_logging_object": {
|
|
"model_parameters": {"user": "test_user"},
|
|
"metadata": {"key_1": "value_1", "key_2": None},
|
|
"call_type": "completion",
|
|
},
|
|
"optional_params": {
|
|
"max_tokens": "100",
|
|
"temperature": "1",
|
|
"top_p": "5",
|
|
"stream": False,
|
|
"user": "test_user",
|
|
"tools": [
|
|
{
|
|
"function": {
|
|
"name": "get_weather",
|
|
"description": "Fetches weather details.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"location": {
|
|
"type": "string",
|
|
"description": "City name",
|
|
}
|
|
},
|
|
"required": ["location"],
|
|
},
|
|
}
|
|
}
|
|
],
|
|
"functions": [{"name": "get_weather"}, {"name": "get_stock_price"}],
|
|
},
|
|
"litellm_params": {"custom_llm_provider": "openai"},
|
|
}
|
|
|
|
# Simulated LLM response object
|
|
response_obj = ModelResponse(
|
|
usage={"total_tokens": 100, "completion_tokens": 60, "prompt_tokens": 40},
|
|
choices=[
|
|
Choices(message={"role": "assistant", "content": "Basic Response Content"})
|
|
],
|
|
model="gpt-4o",
|
|
id="chatcmpl-ID",
|
|
)
|
|
|
|
# Apply attribute setting via ArizeLogger
|
|
ArizeLogger.set_arize_attributes(span, kwargs, response_obj)
|
|
|
|
# Validate that the expected number of attributes were set
|
|
assert span.set_attribute.call_count == 26
|
|
|
|
# Metadata attached to the span
|
|
span.set_attribute.assert_any_call(
|
|
SpanAttributes.METADATA, json.dumps({"key_1": "value_1", "key_2": None})
|
|
)
|
|
|
|
# Basic LLM information
|
|
span.set_attribute.assert_any_call(SpanAttributes.LLM_MODEL_NAME, "gpt-4o")
|
|
span.set_attribute.assert_any_call("llm.request.type", "completion")
|
|
span.set_attribute.assert_any_call(SpanAttributes.LLM_PROVIDER, "openai")
|
|
|
|
# LLM generation parameters
|
|
span.set_attribute.assert_any_call("llm.request.max_tokens", "100")
|
|
span.set_attribute.assert_any_call("llm.request.temperature", "1")
|
|
span.set_attribute.assert_any_call("llm.request.top_p", "5")
|
|
|
|
# Streaming and user info
|
|
span.set_attribute.assert_any_call("llm.is_streaming", "False")
|
|
span.set_attribute.assert_any_call("llm.user", "test_user")
|
|
|
|
# Response metadata
|
|
span.set_attribute.assert_any_call("llm.response.id", "chatcmpl-ID")
|
|
span.set_attribute.assert_any_call("llm.response.model", "gpt-4o")
|
|
# Span kind is set to TOOL when tools are present
|
|
span.set_attribute.assert_any_call(SpanAttributes.OPENINFERENCE_SPAN_KIND, "TOOL")
|
|
|
|
# Request message content and metadata
|
|
span.set_attribute.assert_any_call(
|
|
SpanAttributes.INPUT_VALUE, "Basic Request Content"
|
|
)
|
|
span.set_attribute.assert_any_call(
|
|
f"{SpanAttributes.LLM_INPUT_MESSAGES}.0.{MessageAttributes.MESSAGE_ROLE}",
|
|
"user",
|
|
)
|
|
span.set_attribute.assert_any_call(
|
|
f"{SpanAttributes.LLM_INPUT_MESSAGES}.0.{MessageAttributes.MESSAGE_CONTENT}",
|
|
"Basic Request Content",
|
|
)
|
|
|
|
# Tool call definitions and function names
|
|
span.set_attribute.assert_any_call(
|
|
f"{SpanAttributes.LLM_TOOLS}.0.name", "get_weather"
|
|
)
|
|
span.set_attribute.assert_any_call(
|
|
f"{SpanAttributes.LLM_TOOLS}.0.description",
|
|
"Fetches weather details.",
|
|
)
|
|
span.set_attribute.assert_any_call(
|
|
f"{SpanAttributes.LLM_TOOLS}.0.parameters",
|
|
json.dumps(
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"location": {"type": "string", "description": "City name"}
|
|
},
|
|
"required": ["location"],
|
|
}
|
|
),
|
|
)
|
|
|
|
# Invocation parameters
|
|
span.set_attribute.assert_any_call(
|
|
SpanAttributes.LLM_INVOCATION_PARAMETERS, '{"user": "test_user"}'
|
|
)
|
|
|
|
# User ID
|
|
span.set_attribute.assert_any_call(SpanAttributes.USER_ID, "test_user")
|
|
|
|
# Output message content
|
|
span.set_attribute.assert_any_call(
|
|
SpanAttributes.OUTPUT_VALUE, "Basic Response Content"
|
|
)
|
|
span.set_attribute.assert_any_call(
|
|
f"{SpanAttributes.LLM_OUTPUT_MESSAGES}.0.{MessageAttributes.MESSAGE_ROLE}",
|
|
"assistant",
|
|
)
|
|
span.set_attribute.assert_any_call(
|
|
f"{SpanAttributes.LLM_OUTPUT_MESSAGES}.0.{MessageAttributes.MESSAGE_CONTENT}",
|
|
"Basic Response Content",
|
|
)
|
|
|
|
# Token counts
|
|
span.set_attribute.assert_any_call(SpanAttributes.LLM_TOKEN_COUNT_TOTAL, 100)
|
|
span.set_attribute.assert_any_call(SpanAttributes.LLM_TOKEN_COUNT_COMPLETION, 60)
|
|
span.set_attribute.assert_any_call(SpanAttributes.LLM_TOKEN_COUNT_PROMPT, 40)
|
|
|
|
|
|
def test_arize_set_attributes_responses_api():
|
|
"""
|
|
Test setting attributes for Responses API with mixed output (reasoning + message).
|
|
Verifies that multiple output types are correctly handled.
|
|
"""
|
|
from unittest.mock import MagicMock
|
|
from litellm.types.llms.openai import (
|
|
ResponsesAPIResponse,
|
|
ResponseAPIUsage,
|
|
OutputTokensDetails,
|
|
)
|
|
from openai.types.responses import (
|
|
ResponseReasoningItem,
|
|
ResponseOutputMessage,
|
|
ResponseOutputText,
|
|
)
|
|
from openai.types.responses.response_reasoning_item import Summary
|
|
|
|
span = MagicMock() # Mocked tracing span to test attribute setting
|
|
|
|
# Construct kwargs to simulate a real LLM request scenario
|
|
kwargs = {
|
|
"model": "o3-mini",
|
|
"messages": [{"role": "user", "content": "What is the answer?"}],
|
|
"standard_logging_object": {
|
|
"model_parameters": {"user": "test_user", "stream": True},
|
|
"metadata": {"key_1": "value_1", "key_2": None},
|
|
"call_type": "responses",
|
|
},
|
|
"optional_params": {
|
|
"max_tokens": "100",
|
|
"temperature": "1",
|
|
"top_p": "5",
|
|
"stream": True,
|
|
"user": "test_user",
|
|
},
|
|
"litellm_params": {"custom_llm_provider": "openai"},
|
|
}
|
|
|
|
# Simulate Responses API response with mixed output
|
|
response_obj = ResponsesAPIResponse(
|
|
id="response-123",
|
|
created_at=1625247600,
|
|
output=[
|
|
ResponseReasoningItem(
|
|
id="reasoning-001",
|
|
type="reasoning",
|
|
summary=[
|
|
Summary(text="First, I need to analyze...", type="summary_text")
|
|
],
|
|
),
|
|
ResponseOutputMessage(
|
|
id="msg-001",
|
|
type="message",
|
|
role="assistant",
|
|
status="completed",
|
|
content=[
|
|
ResponseOutputText(
|
|
annotations=[],
|
|
text="The answer is 42",
|
|
type="output_text",
|
|
)
|
|
],
|
|
),
|
|
],
|
|
usage=ResponseAPIUsage(
|
|
input_tokens=120,
|
|
output_tokens=250,
|
|
total_tokens=370,
|
|
output_tokens_details=OutputTokensDetails(reasoning_tokens=180),
|
|
),
|
|
)
|
|
|
|
ArizeLogger.set_arize_attributes(span, kwargs, response_obj)
|
|
|
|
# Verify reasoning summary was set (index 0)
|
|
span.set_attribute.assert_any_call(
|
|
f"{SpanAttributes.LLM_OUTPUT_MESSAGES}.0.{MessageAttributes.MESSAGE_REASONING_SUMMARY}",
|
|
"First, I need to analyze...",
|
|
)
|
|
|
|
# Verify message content was set (index 1)
|
|
span.set_attribute.assert_any_call(SpanAttributes.OUTPUT_VALUE, "The answer is 42")
|
|
span.set_attribute.assert_any_call(
|
|
f"{SpanAttributes.LLM_OUTPUT_MESSAGES}.1.{MessageAttributes.MESSAGE_CONTENT}",
|
|
"The answer is 42",
|
|
)
|
|
span.set_attribute.assert_any_call(
|
|
f"{SpanAttributes.LLM_OUTPUT_MESSAGES}.1.{MessageAttributes.MESSAGE_ROLE}",
|
|
"assistant",
|
|
)
|
|
|
|
# Verify token counts including reasoning tokens
|
|
span.set_attribute.assert_any_call(SpanAttributes.LLM_TOKEN_COUNT_TOTAL, 370)
|
|
span.set_attribute.assert_any_call(SpanAttributes.LLM_TOKEN_COUNT_COMPLETION, 250)
|
|
span.set_attribute.assert_any_call(SpanAttributes.LLM_TOKEN_COUNT_PROMPT, 120)
|
|
span.set_attribute.assert_any_call(
|
|
SpanAttributes.LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING, 180
|
|
)
|
|
|
|
|
|
def test_set_usage_outputs_pydantic_completion_usage():
|
|
"""
|
|
Regression test for https://github.com/BerriAI/litellm/issues/13672
|
|
|
|
`_set_usage_outputs` previously called `usage.get(...)` which crashes when
|
|
`usage` is a plain Pydantic model (e.g. openai.types.completion_usage.CompletionUsage)
|
|
that does not implement dict-style `.get()`. Same crash for nested
|
|
`output_tokens_details` / `completion_tokens_details`.
|
|
|
|
The function must:
|
|
1. Read total/prompt/completion tokens from a Pydantic usage without `.get`.
|
|
2. Read reasoning_tokens from `completion_tokens_details` (chat completions API)
|
|
OR `output_tokens_details` (responses API), even when those nested objects
|
|
are Pydantic models without `.get`.
|
|
3. Not raise AttributeError; not call span.record_exception.
|
|
"""
|
|
from unittest.mock import MagicMock
|
|
|
|
from openai.types.completion_usage import (
|
|
CompletionTokensDetails,
|
|
CompletionUsage,
|
|
)
|
|
|
|
from litellm.integrations.arize._utils import _set_usage_outputs
|
|
|
|
span = MagicMock()
|
|
|
|
# Plain OpenAI Pydantic model — has no `.get()`
|
|
usage = CompletionUsage(
|
|
completion_tokens=60,
|
|
prompt_tokens=40,
|
|
total_tokens=100,
|
|
completion_tokens_details=CompletionTokensDetails(reasoning_tokens=25),
|
|
)
|
|
assert not hasattr(usage, "get"), "precondition: CompletionUsage must lack .get"
|
|
|
|
response_obj = {"usage": usage}
|
|
|
|
# Must not raise
|
|
_set_usage_outputs(span, response_obj, SpanAttributes)
|
|
|
|
span.set_attribute.assert_any_call(SpanAttributes.LLM_TOKEN_COUNT_TOTAL, 100)
|
|
span.set_attribute.assert_any_call(SpanAttributes.LLM_TOKEN_COUNT_PROMPT, 40)
|
|
span.set_attribute.assert_any_call(SpanAttributes.LLM_TOKEN_COUNT_COMPLETION, 60)
|
|
# reasoning_tokens for chat completions live in completion_tokens_details
|
|
span.set_attribute.assert_any_call(
|
|
SpanAttributes.LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING, 25
|
|
)
|
|
|
|
|
|
def test_set_usage_outputs_pydantic_response_api_usage():
|
|
"""
|
|
Same crash also affects Responses API with `output_tokens_details` as a
|
|
Pydantic model that lacks `.get()`. Verifies the responses-API path.
|
|
"""
|
|
from unittest.mock import MagicMock
|
|
|
|
from litellm.integrations.arize._utils import _set_usage_outputs
|
|
from litellm.types.llms.openai import OutputTokensDetails
|
|
|
|
# Build an object that mimics openai ResponsesAPI usage but lacks `.get`
|
|
# (uses a plain class — not BaseLiteLLMOpenAIResponseObject)
|
|
class PlainResponsesUsage:
|
|
def __init__(self):
|
|
self.total_tokens = 370
|
|
self.input_tokens = 120
|
|
self.output_tokens = 250
|
|
self.output_tokens_details = OutputTokensDetails(reasoning_tokens=180)
|
|
|
|
usage = PlainResponsesUsage()
|
|
assert not hasattr(usage, "get")
|
|
|
|
span = MagicMock()
|
|
response_obj = {"usage": usage}
|
|
|
|
_set_usage_outputs(span, response_obj, SpanAttributes)
|
|
|
|
span.set_attribute.assert_any_call(SpanAttributes.LLM_TOKEN_COUNT_TOTAL, 370)
|
|
span.set_attribute.assert_any_call(SpanAttributes.LLM_TOKEN_COUNT_PROMPT, 120)
|
|
span.set_attribute.assert_any_call(SpanAttributes.LLM_TOKEN_COUNT_COMPLETION, 250)
|
|
span.set_attribute.assert_any_call(
|
|
SpanAttributes.LLM_TOKEN_COUNT_COMPLETION_DETAILS_REASONING, 180
|
|
)
|
|
|
|
|
|
class TestArizeLogger(CustomLogger):
|
|
"""
|
|
Custom logger implementation to capture standard_callback_dynamic_params.
|
|
Used to verify that dynamic config keys are being passed to callbacks.
|
|
"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.standard_callback_dynamic_params: Optional[
|
|
StandardCallbackDynamicParams
|
|
] = None
|
|
|
|
async def async_log_success_event(self, kwargs, response_obj, start_time, end_time):
|
|
# Capture dynamic params and print them for verification
|
|
print("logged kwargs", json.dumps(kwargs, indent=4, default=str))
|
|
self.standard_callback_dynamic_params = kwargs.get(
|
|
"standard_callback_dynamic_params"
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_arize_dynamic_params():
|
|
"""
|
|
Test to ensure that dynamic Arize keys (API key and space key)
|
|
are received inside the callback logger at runtime.
|
|
"""
|
|
test_arize_logger = TestArizeLogger()
|
|
litellm.callbacks = [test_arize_logger]
|
|
|
|
# Perform a mocked async completion call to trigger logging
|
|
await litellm.acompletion(
|
|
model="gpt-4o",
|
|
messages=[{"role": "user", "content": "Basic Request Content"}],
|
|
mock_response="test",
|
|
arize_api_key="test_api_key_dynamic",
|
|
arize_space_key="test_space_key_dynamic",
|
|
)
|
|
|
|
# Allow for async propagation
|
|
await asyncio.sleep(2)
|
|
|
|
# Assert dynamic parameters were received in the callback
|
|
assert test_arize_logger.standard_callback_dynamic_params is not None
|
|
assert (
|
|
test_arize_logger.standard_callback_dynamic_params.get("arize_api_key")
|
|
== "test_api_key_dynamic"
|
|
)
|
|
assert (
|
|
test_arize_logger.standard_callback_dynamic_params.get("arize_space_key")
|
|
== "test_space_key_dynamic"
|
|
)
|
|
|
|
|
|
def test_construct_dynamic_arize_headers():
|
|
"""
|
|
Test the construct_dynamic_arize_headers method with various input scenarios.
|
|
Ensures that dynamic Arize headers are properly constructed from callback parameters.
|
|
"""
|
|
from litellm.types.utils import StandardCallbackDynamicParams
|
|
|
|
# Test with all parameters present
|
|
dynamic_params_full = StandardCallbackDynamicParams(
|
|
arize_api_key="test_api_key", arize_space_id="test_space_id"
|
|
)
|
|
arize_logger = ArizeLogger()
|
|
|
|
headers = arize_logger.construct_dynamic_otel_headers(dynamic_params_full)
|
|
expected_headers = {"api_key": "test_api_key", "arize-space-id": "test_space_id"}
|
|
assert headers == expected_headers
|
|
|
|
# Test with only space_id
|
|
dynamic_params_space_id_only = StandardCallbackDynamicParams(
|
|
arize_space_id="test_space_id"
|
|
)
|
|
|
|
headers = arize_logger.construct_dynamic_otel_headers(dynamic_params_space_id_only)
|
|
expected_headers = {"arize-space-id": "test_space_id"}
|
|
assert headers == expected_headers
|
|
|
|
# Test with empty parameters dict
|
|
dynamic_params_empty = StandardCallbackDynamicParams()
|
|
|
|
headers = arize_logger.construct_dynamic_otel_headers(dynamic_params_empty)
|
|
assert headers == {}
|
|
|
|
# test with space key and api key
|
|
dynamic_params_space_key_and_api_key = StandardCallbackDynamicParams(
|
|
arize_space_key="test_space_key", arize_api_key="test_api_key"
|
|
)
|
|
headers = arize_logger.construct_dynamic_otel_headers(
|
|
dynamic_params_space_key_and_api_key
|
|
)
|
|
expected_headers = {"arize-space-id": "test_space_key", "api_key": "test_api_key"}
|