mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-02 07:13:02 +00:00
* test: migrate selected llm translation contracts * test: migrate anthropic completion translation tests * test: migrate bedrock and cohere translation tests * test: tighten bedrock translation migration * test: migrate azure and openai model translation tests * test: migrate openai and small-provider translation tests * test: migrate gemini and vertex translation tests * test: migrate optional params and rerank translation tests * test: migrate huggingface, json and openrouter translation tests * test: fix openrouter integration expectations * test: migrate databricks, xai, cometapi and fireworks translation tests * test: migrate ovhcloud, prompt and small provider translation tests * test: split reasoning effort grid into unit and e2e * test: migrate router and realtime guardrail translation tests * test: migrate google interactions translation tests * test: migrate a2a, evals, skills and hosted vllm translation tests * test: remove remaining llm translation suite * test: tighten migrated llm translation unit tests and land missing rows Restores rows the first pass dropped or weakened (bedrock agentcore and openwebui thinking stream, anthropic prefix, citations and tool_choice, azure agents handler, huggingface bare api_base, optional params num_retries, alternating roles, rerank response type), replaces truthiness and call-count checks with whole-value assertions, and moves tests to the mirror file of the code they cover * test: move cohere, databricks, fireworks and gemini rows to mirror files over mocked HTTP Restores three TestCohereV2Transform methods the first pass nested out of collection, replaces direct transform calls with respx-backed completion calls, and drops the databricks and fireworks files that sat outside their mirror paths * test: close lost-intent gaps found in review of the llm translation migration Points the hosted-mock guard at the triton test's new integration path, restores exact rerank result assertions for infinity, drops the reasoning e2e fallback that passed for any multi-line answer, and adds the mixtral invoke system prompt variants, eval deletion, and async pdf url download that the legacy suite exercised * test: assert provider acceptance in the reasoning effort e2e The live half of the split grid proves each route accepts reasoning_effort. claude-fable-5-1 on the direct route answers without surfacing reasoning content, so a reasoning-presence check either fails on head or needs a fallback that passes for any answer. Wire shape stays covered by the unit grid * test: cover async and stream-end paths the legacy translation suite exercised Adds async variants for bedrock converse file and thinking content, bedrock embedding timeout and region resolution, interaction stream termination and success logging, xai reasoning token folding, and bytes chunks with a prefix before data:. Also scopes the vertex safety settings global in test_vertex_safety_settings with monkeypatch so it stops leaking into gemini request bodies on the same worker * test: type the new llm translation e2e calls and mark moved fixture dirs as packages * test: name the reasoning effort e2e models as module constants * test: assert reasoning content, register the new gemini coverage ids and give the guardrail config e2e a unique name * test: block the prompt the shared guardrail denies and drop the reasoning presence check * test: drop the duplicate litellm import left by the main sync --------- Co-authored-by: yuneng <yuneng@berri.ai>
260 lines
7.7 KiB
Python
260 lines
7.7 KiB
Python
"""
|
|
Test LiteLLM Skills SDK with custom_llm_provider=litellm_proxy
|
|
|
|
Tests the SDK-level skills methods when using the LiteLLM database backend:
|
|
1. Create a skill using SDK and verify it was stored correctly
|
|
2. List skills using SDK
|
|
3. Get a skill by ID using SDK
|
|
4. Delete a skill using SDK
|
|
5. Skills injection hook correctly resolves skills from database
|
|
"""
|
|
|
|
import os
|
|
import zipfile
|
|
from contextlib import contextmanager
|
|
from io import BytesIO
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
import litellm
|
|
from litellm.caching.caching import DualCache
|
|
from litellm.proxy import proxy_server
|
|
from litellm.proxy._types import UserAPIKeyAuth
|
|
from litellm.proxy.utils import PrismaClient, ProxyLogging
|
|
from litellm.types.utils import LlmProviders
|
|
import openai
|
|
|
|
proxy_logging_obj = ProxyLogging(user_api_key_cache=DualCache())
|
|
|
|
|
|
@contextmanager
|
|
def create_skill_zip(skill_name: str):
|
|
"""
|
|
Helper context manager to create a zip file for a skill.
|
|
|
|
Args:
|
|
skill_name: Name of the skill directory in test_skills_data/
|
|
|
|
Yields:
|
|
Tuple of (file handle, file content bytes)
|
|
|
|
The zip file is automatically cleaned up after use.
|
|
"""
|
|
test_dir = Path(__file__).parent / "test_skills_data"
|
|
skill_dir = test_dir / skill_name
|
|
|
|
# Create a zip file containing the skill directory
|
|
zip_path = test_dir / f"{skill_name}.zip"
|
|
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zip_file:
|
|
zip_file.write(skill_dir, arcname=skill_name)
|
|
zip_file.write(skill_dir / "SKILL.md", arcname=f"{skill_name}/SKILL.md")
|
|
|
|
try:
|
|
with open(zip_path, "rb") as f:
|
|
content = f.read()
|
|
f.seek(0)
|
|
yield f, content
|
|
finally:
|
|
# Clean up zip file
|
|
if zip_path.exists():
|
|
zip_path.unlink()
|
|
|
|
|
|
@pytest.fixture
|
|
def prisma_client():
|
|
"""Set up prisma client for tests."""
|
|
from litellm.proxy.proxy_cli import append_query_params
|
|
|
|
params = {"connection_limit": 100, "pool_timeout": 60}
|
|
database_url = os.getenv("DATABASE_URL")
|
|
modified_url = append_query_params(database_url, params)
|
|
os.environ["DATABASE_URL"] = modified_url
|
|
|
|
prisma_client = PrismaClient(
|
|
database_url=os.environ["DATABASE_URL"], proxy_logging_obj=proxy_logging_obj
|
|
)
|
|
|
|
return prisma_client
|
|
|
|
|
|
@pytest.mark.skip(reason="Requires reliable external DB connection (prisma).")
|
|
@pytest.mark.asyncio
|
|
async def test_create_skill_sdk(prisma_client):
|
|
"""
|
|
Test creating a skill using SDK with custom_llm_provider=litellm_proxy.
|
|
|
|
Verifies that:
|
|
- Skill is created with correct display_title
|
|
- Skill ID is generated and returned
|
|
- Skill response has correct type
|
|
"""
|
|
setattr(proxy_server, "prisma_client", prisma_client)
|
|
await proxy_server.prisma_client.connect()
|
|
|
|
from litellm.skills.main import acreate_skill, adelete_skill
|
|
|
|
# Create a skill using SDK
|
|
skill = await acreate_skill(
|
|
display_title="SDK Test Skill",
|
|
extra_body={
|
|
"description": "A test skill created via SDK",
|
|
"instructions": "Use this skill for SDK testing",
|
|
},
|
|
custom_llm_provider=LlmProviders.LITELLM_PROXY.value,
|
|
)
|
|
|
|
# Verify skill was created correctly
|
|
assert skill is not None
|
|
assert skill.id is not None
|
|
assert skill.id.startswith("litellm_skill")
|
|
assert skill.display_title == "SDK Test Skill"
|
|
assert skill.type == "skill"
|
|
assert skill.source == "custom"
|
|
|
|
# Clean up
|
|
await adelete_skill(
|
|
skill_id=skill.id,
|
|
custom_llm_provider=LlmProviders.LITELLM_PROXY.value,
|
|
)
|
|
|
|
|
|
@pytest.mark.skip(reason="Requires reliable external DB connection (prisma).")
|
|
@pytest.mark.asyncio
|
|
async def test_list_skills_sdk(prisma_client):
|
|
"""
|
|
Test listing skills using SDK with custom_llm_provider=litellm_proxy.
|
|
|
|
Verifies that:
|
|
- Multiple skills can be created
|
|
- List returns the created skills
|
|
"""
|
|
setattr(proxy_server, "prisma_client", prisma_client)
|
|
await proxy_server.prisma_client.connect()
|
|
|
|
from litellm.skills.main import acreate_skill, adelete_skill, alist_skills
|
|
|
|
# Create multiple skills
|
|
created_skill_ids = []
|
|
for i in range(3):
|
|
skill = await acreate_skill(
|
|
display_title=f"List Test Skill {i}",
|
|
extra_body={
|
|
"description": f"Test skill {i} for list test",
|
|
},
|
|
custom_llm_provider=LlmProviders.LITELLM_PROXY.value,
|
|
)
|
|
created_skill_ids.append(skill.id)
|
|
|
|
# List skills using SDK
|
|
response = await alist_skills(
|
|
limit=10,
|
|
custom_llm_provider=LlmProviders.LITELLM_PROXY.value,
|
|
)
|
|
|
|
# Verify we got skills back
|
|
assert response is not None
|
|
assert response.data is not None
|
|
assert len(response.data) >= 3
|
|
|
|
# Verify our created skills are in the list
|
|
skill_ids_in_list = [s.id for s in response.data]
|
|
for created_id in created_skill_ids:
|
|
assert created_id in skill_ids_in_list
|
|
|
|
# Clean up
|
|
for skill_id in created_skill_ids:
|
|
await adelete_skill(
|
|
skill_id=skill_id,
|
|
custom_llm_provider=LlmProviders.LITELLM_PROXY.value,
|
|
)
|
|
|
|
|
|
@pytest.mark.skip(reason="Requires reliable external DB connection (prisma).")
|
|
@pytest.mark.asyncio
|
|
async def test_get_skill_sdk(prisma_client):
|
|
"""
|
|
Test getting a skill by ID using SDK with custom_llm_provider=litellm_proxy.
|
|
|
|
Verifies that:
|
|
- Skill can be retrieved by ID
|
|
- Retrieved skill has correct data
|
|
"""
|
|
setattr(proxy_server, "prisma_client", prisma_client)
|
|
await proxy_server.prisma_client.connect()
|
|
|
|
from litellm.skills.main import acreate_skill, adelete_skill, aget_skill
|
|
|
|
# Create a skill
|
|
created_skill = await acreate_skill(
|
|
display_title="Get Test Skill",
|
|
extra_body={
|
|
"description": "A skill for get test",
|
|
},
|
|
custom_llm_provider=LlmProviders.LITELLM_PROXY.value,
|
|
)
|
|
|
|
# Get the skill by ID using SDK
|
|
retrieved_skill = await aget_skill(
|
|
skill_id=created_skill.id,
|
|
custom_llm_provider=LlmProviders.LITELLM_PROXY.value,
|
|
)
|
|
|
|
# Verify retrieved skill matches created skill
|
|
assert retrieved_skill is not None
|
|
assert retrieved_skill.id == created_skill.id
|
|
assert retrieved_skill.display_title == "Get Test Skill"
|
|
|
|
# Clean up
|
|
await adelete_skill(
|
|
skill_id=created_skill.id,
|
|
custom_llm_provider=LlmProviders.LITELLM_PROXY.value,
|
|
)
|
|
|
|
|
|
@pytest.mark.skip(reason="Requires reliable external DB connection (prisma).")
|
|
@pytest.mark.asyncio
|
|
async def test_delete_skill_sdk(prisma_client):
|
|
"""
|
|
Test deleting a skill using SDK with custom_llm_provider=litellm_proxy.
|
|
|
|
Verifies that:
|
|
- Skill can be deleted by ID
|
|
- Deleted skill cannot be retrieved
|
|
"""
|
|
setattr(proxy_server, "prisma_client", prisma_client)
|
|
await proxy_server.prisma_client.connect()
|
|
|
|
from litellm.skills.main import acreate_skill, adelete_skill, aget_skill
|
|
|
|
# Create a skill
|
|
created_skill = await acreate_skill(
|
|
display_title="Delete Test Skill",
|
|
extra_body={
|
|
"description": "A skill to be deleted",
|
|
},
|
|
custom_llm_provider=LlmProviders.LITELLM_PROXY.value,
|
|
)
|
|
|
|
# Verify skill exists
|
|
retrieved = await aget_skill(
|
|
skill_id=created_skill.id,
|
|
custom_llm_provider=LlmProviders.LITELLM_PROXY.value,
|
|
)
|
|
assert retrieved is not None
|
|
|
|
# Delete the skill using SDK
|
|
result = await adelete_skill(
|
|
skill_id=created_skill.id,
|
|
custom_llm_provider=LlmProviders.LITELLM_PROXY.value,
|
|
)
|
|
assert result.id == created_skill.id
|
|
assert result.type == "skill_deleted"
|
|
|
|
# Verify skill no longer exists
|
|
with pytest.raises(openai.APIError):
|
|
await aget_skill(
|
|
skill_id=created_skill.id,
|
|
custom_llm_provider=LlmProviders.LITELLM_PROXY.value,
|
|
)
|