litellm/tests/integration/test_oci_integration.py
Fede Kamelhar 68759a239f
fix(oci): drop non-function tools under drop_params and gate reasoning_effort on declared levels (#40732)
* fix(oci): skip non-function tools and gate reasoning_effort on models that reject it

Fixes #31449.

Two OCI GENERIC request-building defects that made Grok-on-OCI unusable
for tool-using coding-agent clients (Codex over the Responses API, Claude
Code over the Anthropic Messages API):

1. `adapt_tool_definition_to_oci_standard` raised `OCIError("OCI only
   supports function tools")` for any tool whose type is not "function",
   aborting the whole request when a client mixed a built-in/server tool
   in with its function tools. Non-function tools are now skipped with a
   warning; if none remain, `tools`/`toolChoice` are omitted from the
   request instead of sending an empty array.

2. The GENERIC param map forwarded `reasoning_effort` to every non-Cohere
   model, but OCI rejects it for all served xAI Grok models
   (`Model grok-4.20 does not support parameter reasoningEffort`) and for
   OpenAI's non-reasoning models (`Unrecognized request argument supplied:
   reasoning_effort` on gpt-4o / gpt-4.1); gpt-5, the o-series, gpt-oss-*
   and Meta/Google models accept it. `_model_supports_reasoning_effort`
   encodes that, `get_supported_openai_params` stops advertising the param
   for those models, and `map_openai_params` follows the standard
   unsupported-param contract (dropped under drop_params, clear error
   otherwise) so `drop_params` / `additional_drop_params` now work as
   documented instead of the 400 surfacing from the service.

Behaviour verified against OCI GenAI in us-chicago-1 (2026-09-11) per model.
The two existing reasoning_effort normalisation tests move from a Grok
model to openai.gpt-5, which is the case they exercise. 21 new tests; the
OCI suite is 406 passed.

* fix(oci): drive reasoning_effort gating from the model catalog and drop tool_choice for skipped tools

Review follow-ups on #40732:

- `_model_supports_reasoning_effort` now reads the `supports_reasoning` flag
  of the model's `oci/` catalog entry through `get_model_info` instead of
  hardcoding xAI / gpt-oss branches. Catalogued models without the flag
  (xAI Grok, Meta Llama) no longer receive `reasoningEffort`; models absent
  from the catalog keep the historical pass-through.
- Catalog: flag `oci/google.gemini-2.5-{flash,pro,flash-lite}` as reasoning
  models and add `oci/openai.gpt-oss-120b` / `oci/openai.gpt-oss-20b`
  (pricing from Oracle's public price list, 128K context per OCI docs).
- `_model_uses_max_completion_tokens` routes on the `openai.*` prefix only.
  OCI-hosted Gemini accepts `maxCompletionTokens` but ignores it (the output
  cap silently disappears) and Llama rejects it with 400, so the catalog's
  reasoning flag must not drive that switch.
- After tool adaptation, a `FUNCTION` tool_choice naming a tool that was
  skipped (built-in / server tools) is dropped with a warning instead of
  being sent to OCI, which rejects the unmatched choice.

Verified live against OCI GenAI (us-chicago-1): Grok with mixed tools plus a
forced built-in now completes and still calls the function tool; Gemini and
gpt-oss forward reasoningEffort with maxTokens honoured; Llama drops it under
drop_params; gpt-5 keeps maxCompletionTokens.

* style(oci): use an immutable set for the tool-name lookup

Keeps the type-discipline gate (LIT002, mutable collections) within its
budget; the lookup is read-only.

* fix(oci): fail closed when a forced tool_choice names a tool OCI will not receive

Review follow-ups on #40732:

- When tool_choice forces a function that is not among the adapted tools
  (a skipped built-in or a misspelled name), send `toolChoice: NONE` instead
  of removing the choice. Falling back to the service default would let the
  model call any other supplied function even though the caller forced a
  specific one, which an agent processing untrusted content must not do.
- Move the #31449 regression tests into the mapped test module
  (`test_oci_chat_transformation.py`) instead of a separate file.

Verified live against OCI GenAI: Grok and Llama with a forced skipped
built-in now complete with no tool call; a forced kept function is still
honoured.

* test(oci): point reasoning_effort comments at the in-module gating tests

The referenced module was folded into this file.

* fix(oci): drop non-function tools only under drop_params and gate reasoning_effort on declared levels

Non-function tools now follow the unsupported-param contract: a 400 naming
drop_params without it, and dropped with a warning under it, with a forced
dropped tool turning tool calls off. reasoning_effort is gated on the model's
reasoning_effort_levels catalog entry, so OCI Llama keeps forwarding it and the
Grok 4.20 models OCI serves, now in the catalog with their Oracle prices, stop
sending a param the service rejects

* fix(oci): gate reasoning_effort on oci/xai.grok-4.20, which rejects it

* fix(oci): disable tool calls when tool_choice forces a dropped custom tool by name

* fix(oci): drop tool_choice that forces a dropped tool and keep a kept function's name

* fix(oci): keep Gemini 2.5 Flash Lite unflagged for reasoning so released proxies keep its max_tokens cap

* fix(oci): answer 400 when a request depends on a dropped tool and leave Gemini max_tokens routing to its own PR

Under drop_params, non-function tools still drop when function tools remain and
tool_choice does not force a dropped tool. A request whose every tool is dropped,
or whose tool_choice forces one, now answers 400 instead of a tool-less reply the
model can fill with invented results, or a swap to another tool.

The maxTokens routing change for OCI Gemini goes back to base, since it is
outside this fix and an open PR carries it.

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-15 22:20:02 +00:00

665 lines
22 KiB
Python

"""
OCI Generative AI — end-to-end integration tests.
These tests make REAL calls to OCI. They are skipped automatically when the
standard ~/.oci/config is absent or when OCI_TEST_COMPARTMENT_ID is not set.
Prerequisites
-------------
- ~/.oci/config with a valid [DEFAULT] profile
- Private key referenced by key_file in that profile
- Sufficient IAM policies to call the Generative AI inference service
Environment variables (all optional — fall back to ~/.oci/config values):
OCI_TEST_REGION OCI region (default: us-chicago-1)
OCI_TEST_COMPARTMENT_ID compartment OCID (default: tenancy root from config)
Run only these tests:
pytest tests/integration/test_oci_integration.py -v
"""
import math
import os
from typing import NamedTuple, Optional
import pytest
# ---------------------------------------------------------------------------
# Fixtures / helpers
# ---------------------------------------------------------------------------
OCI_CONFIG_FILE = os.path.expanduser("~/.oci/config")
_OCI_AVAILABLE = os.path.isfile(OCI_CONFIG_FILE)
pytestmark = pytest.mark.skipif(
not _OCI_AVAILABLE,
reason="~/.oci/config not found — skipping OCI integration tests",
)
def _load_oci_config():
"""Load OCI config from the profile named by ``OCI_CONFIG_PROFILE`` env var,
falling back to ``[DEFAULT]``. Lets CI/local runs target a specific profile
without needing a ``[DEFAULT]`` section in ``~/.oci/config``."""
oci = pytest.importorskip("oci")
profile = os.environ.get("OCI_CONFIG_PROFILE", "DEFAULT")
return oci.config.from_file(profile_name=profile)
@pytest.fixture(scope="module")
def oci_signer():
"""Return an oci.Signer (or SecurityTokenSigner for session-token profiles)
built from ~/.oci/config — profile chosen via OCI_CONFIG_PROFILE."""
oci = pytest.importorskip("oci")
config = _load_oci_config()
# Session-token profiles carry a `security_token_file` instead of a user
# OCID; build the corresponding signer in that case.
if "security_token_file" in config:
with open(os.path.expanduser(config["security_token_file"])) as f:
token = f.read().strip()
private_key = oci.signer.load_private_key_from_file(
config["key_file"], config.get("pass_phrase")
)
return oci.auth.signers.SecurityTokenSigner(token, private_key)
return oci.Signer(
tenancy=config["tenancy"],
user=config["user"],
fingerprint=config["fingerprint"],
private_key_file_location=config["key_file"],
)
@pytest.fixture(scope="module")
def oci_params(oci_signer) -> dict:
"""Common OCI call-time parameters shared by all tests."""
config = _load_oci_config()
compartment_id = os.environ.get("OCI_TEST_COMPARTMENT_ID", config["tenancy"])
region = os.environ.get("OCI_TEST_REGION", "us-chicago-1")
return {
"oci_signer": oci_signer,
"oci_compartment_id": compartment_id,
"oci_region": region,
}
# ---------------------------------------------------------------------------
# Model registry
#
# Each entry drives the runtime pivot inside OCI's own transformation layer —
# the tests themselves are format-agnostic. Per-model quirks are captured in
# the config fields below rather than in separate test classes.
# ---------------------------------------------------------------------------
class _M(NamedTuple):
"""Per-model test configuration."""
model: str
max_tokens: int
# Reasoning models (Gemini 2.5, Grok mini) may return None content when the
# reasoning budget is exhausted before the answer token budget starts.
reasoning: bool = False
# tool_choice value to send; None means omit the parameter entirely.
tool_choice: Optional[str] = "auto"
# Whether to include the model in tool-use parametrize list.
supports_tool_use: bool = True
# All chat models under test.
CHAT_MODELS = [
pytest.param(_M("meta.llama-3.3-70b-instruct", 64), id="meta"),
pytest.param(_M("google.gemini-2.5-flash", 200, reasoning=True), id="google"),
pytest.param(_M("xai.grok-3-mini", 100, reasoning=True), id="xai"),
pytest.param(_M("cohere.command-latest", 64, tool_choice=None), id="cohere"),
]
# Subset of models that reliably support tool use in OCI.
# xAI Grok mini is omitted — OCI does not expose tool-use for it yet.
TOOL_USE_MODELS = [
pytest.param(_M("meta.llama-3.3-70b-instruct", 100), id="meta"),
pytest.param(_M("cohere.command-latest", 200, tool_choice=None), id="cohere"),
pytest.param(_M("google.gemini-2.5-flash", 200, reasoning=True), id="google"),
]
# Simple weather tool used by all tool-use tests.
_WEATHER_TOOL = {
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string", "description": "The city name."}},
"required": ["city"],
},
},
}
# ---------------------------------------------------------------------------
# Sync chat tests — model list drives the pivot, not separate test classes
# ---------------------------------------------------------------------------
@pytest.mark.parametrize("m", CHAT_MODELS)
def test_basic_completion(m: _M, oci_params):
import litellm
resp = litellm.completion(
model=f"oci/{m.model}",
messages=[{"role": "user", "content": "Reply with only the word: pong"}],
max_tokens=m.max_tokens,
**oci_params,
)
assert resp.choices[0].finish_reason is not None
assert resp.usage.prompt_tokens > 0
if not m.reasoning:
assert resp.choices[0].message.content is not None
@pytest.mark.parametrize("m", CHAT_MODELS)
def test_usage_populated(m: _M, oci_params):
import litellm
resp = litellm.completion(
model=f"oci/{m.model}",
messages=[{"role": "user", "content": "What is 2+2?"}],
max_tokens=m.max_tokens,
**oci_params,
)
assert resp.usage.prompt_tokens > 0
assert resp.usage.total_tokens >= resp.usage.prompt_tokens
@pytest.mark.parametrize("m", CHAT_MODELS)
def test_system_message(m: _M, oci_params):
import litellm
resp = litellm.completion(
model=f"oci/{m.model}",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Say hello."},
],
max_tokens=m.max_tokens,
**oci_params,
)
assert resp.choices[0].finish_reason is not None
@pytest.mark.parametrize("m", CHAT_MODELS)
def test_streaming(m: _M, oci_params):
import litellm
chunks = list(
litellm.completion(
model=f"oci/{m.model}",
messages=[{"role": "user", "content": "Count to 3."}],
max_tokens=m.max_tokens,
stream=True,
**oci_params,
)
)
assert len(chunks) > 0
# Reasoning models may stream only reasoning tokens and return empty content.
if not m.reasoning:
content = "".join(c.choices[0].delta.content or "" for c in chunks if c.choices)
assert len(content) > 0
@pytest.mark.parametrize(
"model",
["cohere.command-latest", "cohere.command-r-plus-08-2024"],
)
def test_cohere_streaming_no_doubling(model, oci_params):
"""Regression: OCI Cohere's terminal SSE event re-sends the full assembled
response in `text` alongside a populated `chatHistory`. Emitting that text
as another delta would concatenate the whole response onto the
already-streamed output (e.g. "How can I help?How can I help?").
Reported by @gotsysdba on PR #25177. Fix: drop terminal text when
`chatHistory` is present in `handle_cohere_stream_chunk`.
"""
import litellm
streamed = "".join(
(c.choices[0].delta.content or "")
for c in litellm.completion(
model=f"oci/{model}",
messages=[{"role": "user", "content": "Hello!"}],
max_tokens=64,
stream=True,
**oci_params,
)
if c.choices
).strip()
assert streamed, "expected non-empty streamed content"
# Compare against a non-streamed call. With the doubling bug the streamed
# assembly is ~2x the real response; without it the two are the same order
# of magnitude (the model is non-deterministic, so allow generous slack).
non_streamed = (
litellm.completion(
model=f"oci/{model}",
messages=[{"role": "user", "content": "Hello!"}],
max_tokens=64,
**oci_params,
)
.choices[0]
.message.content
or ""
).strip()
assert len(streamed) < 2 * len(non_streamed) + 10, (
f"streamed output appears doubled — "
f"streamed={len(streamed)} chars vs non_streamed={len(non_streamed)} chars\n"
f"streamed: {streamed!r}\n"
f"non_streamed: {non_streamed!r}"
)
# Stronger signal: the very start of the response should not appear twice.
head = streamed[:12]
assert streamed.count(head) == 1, (
f"streamed output contains its own prefix {head!r} more than once — "
f"likely the terminal chunk re-emitted the full response.\n"
f"streamed: {streamed!r}"
)
@pytest.mark.parametrize("m", CHAT_MODELS)
def test_multi_turn(m: _M, oci_params):
import litellm
resp = litellm.completion(
model=f"oci/{m.model}",
messages=[
{"role": "user", "content": "My name is Alice."},
{"role": "assistant", "content": "Nice to meet you, Alice!"},
{"role": "user", "content": "What is my name?"},
],
max_tokens=m.max_tokens,
**oci_params,
)
# Reasoning models may have None content; skip text assertion for them.
content = resp.choices[0].message.content or ""
if not m.reasoning:
assert "Alice" in content
# ---------------------------------------------------------------------------
# Async chat tests
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
@pytest.mark.parametrize("m", CHAT_MODELS)
async def test_async_completion(m: _M, oci_params):
import litellm
resp = await litellm.acompletion(
model=f"oci/{m.model}",
messages=[{"role": "user", "content": "Reply with only the word: pong"}],
max_tokens=m.max_tokens,
**oci_params,
)
assert resp.choices[0].finish_reason is not None
assert resp.usage.total_tokens > 0
if not m.reasoning:
assert resp.choices[0].message.content is not None
@pytest.mark.asyncio
@pytest.mark.parametrize("m", CHAT_MODELS)
async def test_async_streaming(m: _M, oci_params):
import litellm
chunks = []
async for chunk in await litellm.acompletion(
model=f"oci/{m.model}",
messages=[{"role": "user", "content": "Count to 3."}],
max_tokens=m.max_tokens,
stream=True,
**oci_params,
):
chunks.append(chunk)
assert len(chunks) > 0
if not m.reasoning:
content = "".join(c.choices[0].delta.content or "" for c in chunks if c.choices)
assert len(content) > 0
# ---------------------------------------------------------------------------
# Tool-use tests
# ---------------------------------------------------------------------------
def _assert_tool_call(resp, expected_tool: str = "get_weather"):
"""Assert the response contains the expected tool call (or a plain stop)."""
choice = resp.choices[0]
assert choice.finish_reason in ("tool_calls", "stop")
if choice.finish_reason == "tool_calls":
assert choice.message.tool_calls is not None
assert len(choice.message.tool_calls) > 0
assert choice.message.tool_calls[0].function.name == expected_tool
@pytest.mark.parametrize("m", TOOL_USE_MODELS)
def test_tool_use(m: _M, oci_params):
import litellm
call_kwargs = dict(
model=f"oci/{m.model}",
messages=[{"role": "user", "content": "What is the weather in Paris?"}],
tools=[_WEATHER_TOOL],
max_tokens=m.max_tokens,
**oci_params,
)
if m.tool_choice is not None:
call_kwargs["tool_choice"] = m.tool_choice
resp = litellm.completion(**call_kwargs)
_assert_tool_call(resp)
@pytest.mark.asyncio
@pytest.mark.parametrize("m", TOOL_USE_MODELS)
async def test_async_tool_use(m: _M, oci_params):
import litellm
call_kwargs = dict(
model=f"oci/{m.model}",
messages=[{"role": "user", "content": "What is the weather in Berlin?"}],
tools=[_WEATHER_TOOL],
max_tokens=m.max_tokens,
**oci_params,
)
if m.tool_choice is not None:
call_kwargs["tool_choice"] = m.tool_choice
resp = await litellm.acompletion(**call_kwargs)
_assert_tool_call(resp)
# ---------------------------------------------------------------------------
# Reasoning-effort tests (reasoning models only)
# ---------------------------------------------------------------------------
# Reasons at LOW and accepts reasoningEffort NONE on OCI (gpt-oss and Gemini Pro reject NONE, Grok 4.20 the param).
_REASONING_MODEL = "google.gemini-2.5-flash"
@pytest.mark.parametrize("effort", ["low", "medium", "high"])
def test_reasoning_effort_lowercase_accepted(effort, oci_params):
"""OpenAI clients send lowercase reasoning_effort; OCI requires uppercase.
The transform layer should uppercase it transparently."""
import litellm
resp = litellm.completion(
model=f"oci/{_REASONING_MODEL}",
messages=[{"role": "user", "content": "What is 2+2? One word."}],
max_tokens=200,
reasoning_effort=effort,
**oci_params,
)
assert resp.choices[0].finish_reason is not None
assert resp.usage.prompt_tokens > 0
def test_reasoning_effort_disable_mapped_to_none(oci_params):
"""OpenAI's 'disable' maps to OCI's 'NONE'. Without this mapping the
request 400s."""
import litellm
resp = litellm.completion(
model=f"oci/{_REASONING_MODEL}",
messages=[{"role": "user", "content": "What is 2+2? One word."}],
max_tokens=200,
reasoning_effort="disable",
**oci_params,
)
assert resp.choices[0].finish_reason is not None
def test_reasoning_tokens_in_usage(oci_params):
"""OCI returns completionTokensDetails.reasoningTokens on reasoning models;
LiteLLM should surface it on Usage.completion_tokens_details."""
import litellm
resp = litellm.completion(
model=f"oci/{_REASONING_MODEL}",
messages=[{"role": "user", "content": "What is 2+2? One word."}],
max_tokens=200,
reasoning_effort="low",
**oci_params,
)
assert resp.usage.completion_tokens_details is not None
assert resp.usage.completion_tokens_details.reasoning_tokens is not None
assert resp.usage.completion_tokens_details.reasoning_tokens > 0
# ---------------------------------------------------------------------------
# Embedding tests
# ---------------------------------------------------------------------------
class TestOCIEmbeddings:
def test_english_v3_basic(self, oci_params):
import litellm
resp = litellm.embedding(
model="oci/cohere.embed-english-v3.0",
input=["Hello world"],
input_type="SEARCH_DOCUMENT",
**oci_params,
)
assert len(resp.data) == 1
assert len(resp.data[0]["embedding"]) == 1024
assert resp.usage.prompt_tokens > 0
def test_english_v3_batch(self, oci_params):
import litellm
texts = [
"The quick brown fox",
"jumps over the lazy dog",
"Paris is the capital of France",
]
resp = litellm.embedding(
model="oci/cohere.embed-english-v3.0",
input=texts,
input_type="SEARCH_DOCUMENT",
**oci_params,
)
assert len(resp.data) == 3
for i, item in enumerate(resp.data):
assert item["index"] == i
assert len(item["embedding"]) == 1024
def test_multilingual_v3(self, oci_params):
import litellm
resp = litellm.embedding(
model="oci/cohere.embed-multilingual-v3.0",
input=["Bonjour le monde", "Hola mundo"],
input_type="SEARCH_DOCUMENT",
**oci_params,
)
assert len(resp.data) == 2
assert len(resp.data[0]["embedding"]) == 1024
def test_search_query_input_type(self, oci_params):
import litellm
resp = litellm.embedding(
model="oci/cohere.embed-english-v3.0",
input=["What is the capital of France?"],
input_type="SEARCH_QUERY",
**oci_params,
)
assert len(resp.data[0]["embedding"]) == 1024
def test_semantic_similarity(self, oci_params):
"""Semantically similar texts should have higher cosine similarity."""
import litellm
resp = litellm.embedding(
model="oci/cohere.embed-english-v3.0",
input=[
"The cat sat on the mat",
"A feline rested on the rug",
"The stock market crashed today",
],
input_type="SEARCH_DOCUMENT",
**oci_params,
)
def cosine(a, b):
dot = sum(x * y for x, y in zip(a, b))
mag_a = math.sqrt(sum(x**2 for x in a))
mag_b = math.sqrt(sum(x**2 for x in b))
return dot / (mag_a * mag_b)
cat1 = resp.data[0]["embedding"]
cat2 = resp.data[1]["embedding"]
stock = resp.data[2]["embedding"]
sim_cats = cosine(cat1, cat2)
sim_diff = cosine(cat1, stock)
assert (
sim_cats > sim_diff
), f"Expected similar sentences to score higher ({sim_cats:.3f} vs {sim_diff:.3f})"
def test_embed_v4(self, oci_params):
import litellm
resp = litellm.embedding(
model="oci/cohere.embed-v4.0",
input=["Hello world"],
input_type="SEARCH_DOCUMENT",
**oci_params,
)
assert len(resp.data) == 1
assert len(resp.data[0]["embedding"]) == 1536
def test_usage_tokens(self, oci_params):
import litellm
resp = litellm.embedding(
model="oci/cohere.embed-english-v3.0",
input=["short text", "another short text"],
input_type="SEARCH_DOCUMENT",
**oci_params,
)
assert resp.usage.prompt_tokens > 0
assert resp.usage.total_tokens == resp.usage.prompt_tokens
# ---------------------------------------------------------------------------
# Async embedding tests
# ---------------------------------------------------------------------------
class TestOCIAsyncEmbeddings:
@pytest.mark.asyncio
async def test_async_embedding_basic(self, oci_params):
import litellm
resp = await litellm.aembedding(
model="oci/cohere.embed-english-v3.0",
input=["Hello world"],
input_type="SEARCH_DOCUMENT",
**oci_params,
)
assert len(resp.data) == 1
assert len(resp.data[0]["embedding"]) == 1024
assert resp.usage.prompt_tokens > 0
@pytest.mark.asyncio
async def test_async_embedding_batch(self, oci_params):
import litellm
texts = ["The quick brown fox", "jumps over the lazy dog"]
resp = await litellm.aembedding(
model="oci/cohere.embed-english-v3.0",
input=texts,
input_type="SEARCH_DOCUMENT",
**oci_params,
)
assert len(resp.data) == 2
assert all(len(item["embedding"]) == 1024 for item in resp.data)
@pytest.mark.asyncio
async def test_async_embedding_multilingual(self, oci_params):
import litellm
resp = await litellm.aembedding(
model="oci/cohere.embed-multilingual-v3.0",
input=["Bonjour le monde"],
input_type="SEARCH_DOCUMENT",
**oci_params,
)
assert len(resp.data[0]["embedding"]) == 1024
# ---------------------------------------------------------------------------
# Env-var credential path
# ---------------------------------------------------------------------------
class TestOCIEnvVarCredentials:
"""Verify that OCI_* env vars are picked up without explicit params."""
def test_completion_via_env_vars(self, monkeypatch):
"""Completion works when credentials are set through environment variables."""
pytest.importorskip("oci")
config = _load_oci_config()
key_path = os.path.expanduser(config["key_file"])
with open(key_path) as f:
key_pem = f.read()
monkeypatch.setenv("OCI_REGION", "us-chicago-1")
monkeypatch.setenv("OCI_USER", config["user"])
monkeypatch.setenv("OCI_FINGERPRINT", config["fingerprint"])
monkeypatch.setenv("OCI_TENANCY", config["tenancy"])
monkeypatch.setenv("OCI_KEY", key_pem)
monkeypatch.setenv("OCI_COMPARTMENT_ID", config["tenancy"])
import litellm
resp = litellm.completion(
model="oci/meta.llama-3.3-70b-instruct",
messages=[{"role": "user", "content": "Reply with only the word: pong"}],
max_tokens=10,
)
assert resp.choices[0].message.content is not None
def test_embedding_via_env_vars(self, monkeypatch):
pytest.importorskip("oci")
config = _load_oci_config()
key_path = os.path.expanduser(config["key_file"])
with open(key_path) as f:
key_pem = f.read()
monkeypatch.setenv("OCI_REGION", "us-chicago-1")
monkeypatch.setenv("OCI_USER", config["user"])
monkeypatch.setenv("OCI_FINGERPRINT", config["fingerprint"])
monkeypatch.setenv("OCI_TENANCY", config["tenancy"])
monkeypatch.setenv("OCI_KEY", key_pem)
monkeypatch.setenv("OCI_COMPARTMENT_ID", config["tenancy"])
import litellm
resp = litellm.embedding(
model="oci/cohere.embed-english-v3.0",
input=["hello"],
input_type="SEARCH_DOCUMENT",
)
assert len(resp.data[0]["embedding"]) == 1024