chore: cleanup unused scripts and fix misplaced test file (#17611)
Remove scripts/ directory containing unused development/debug scripts: - mock_ibm_guardrails_server.py - test_groq_streaming_issue.py (debug for #12660) - test_mock_ibm_guardrails.py - update_readme_providers_table.py Move misplaced test file to correct location: - test_litellm/ -> tests/test_litellm/ (from PR #17221)
This commit is contained in:
parent
b673177b22
commit
a7ad8a36a4
@ -1,358 +0,0 @@
|
|||||||
"""
|
|
||||||
Mock FastAPI server for IBM FMS Guardrails Orchestrator Detector API.
|
|
||||||
|
|
||||||
This server implements the Detector API endpoints for testing purposes.
|
|
||||||
Based on: https://foundation-model-stack.github.io/fms-guardrails-orchestrator/
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
python scripts/mock_ibm_guardrails_server.py
|
|
||||||
|
|
||||||
The server will run on http://localhost:8001 by default.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import uuid
|
|
||||||
from typing import Any, Dict, List, Optional
|
|
||||||
|
|
||||||
import uvicorn
|
|
||||||
from fastapi import FastAPI, Header, HTTPException, status
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
|
|
||||||
app = FastAPI(
|
|
||||||
title="IBM FMS Guardrails Orchestrator Mock",
|
|
||||||
description="Mock server for testing IBM Guardrails Detector API",
|
|
||||||
version="1.0.0",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Request Models
|
|
||||||
class DetectorParams(BaseModel):
|
|
||||||
"""Parameters specific to the detector."""
|
|
||||||
|
|
||||||
threshold: Optional[float] = Field(None, ge=0.0, le=1.0)
|
|
||||||
custom_param: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
class TextDetectionRequest(BaseModel):
|
|
||||||
"""Request model for text detection."""
|
|
||||||
|
|
||||||
contents: List[str] = Field(..., description="Text content to analyze")
|
|
||||||
detector_params: Optional[DetectorParams] = None
|
|
||||||
|
|
||||||
|
|
||||||
class TextGenerationDetectionRequest(BaseModel):
|
|
||||||
"""Request model for text generation detection."""
|
|
||||||
|
|
||||||
detector_id: str = Field(..., description="ID of the detector to use")
|
|
||||||
prompt: str = Field(..., description="Input prompt")
|
|
||||||
generated_text: str = Field(..., description="Generated text to analyze")
|
|
||||||
detector_params: Optional[DetectorParams] = None
|
|
||||||
|
|
||||||
|
|
||||||
class ContextDetectionRequest(BaseModel):
|
|
||||||
"""Request model for detection with context."""
|
|
||||||
|
|
||||||
detector_id: str = Field(..., description="ID of the detector to use")
|
|
||||||
content: str = Field(..., description="Text content to analyze")
|
|
||||||
context: Optional[Dict[str, Any]] = Field(None, description="Additional context")
|
|
||||||
detector_params: Optional[DetectorParams] = None
|
|
||||||
|
|
||||||
|
|
||||||
# Response Models
|
|
||||||
class Detection(BaseModel):
|
|
||||||
"""Individual detection result."""
|
|
||||||
|
|
||||||
detection_type: str = Field(..., description="Type of detection")
|
|
||||||
detection: bool = Field(..., description="Whether content was detected as harmful")
|
|
||||||
score: float = Field(..., ge=0.0, le=1.0, description="Detection confidence score")
|
|
||||||
start: Optional[int] = Field(None, description="Start position in text")
|
|
||||||
end: Optional[int] = Field(None, description="End position in text")
|
|
||||||
text: Optional[str] = Field(None, description="Detected text segment")
|
|
||||||
evidence: Optional[List[str]] = Field(None, description="Supporting evidence")
|
|
||||||
|
|
||||||
|
|
||||||
class DetectionResponse(BaseModel):
|
|
||||||
"""Response model for detection results."""
|
|
||||||
|
|
||||||
detections: List[Detection] = Field(..., description="List of detections")
|
|
||||||
detection_id: str = Field(..., description="Unique ID for this detection request")
|
|
||||||
|
|
||||||
|
|
||||||
# Mock detector configurations
|
|
||||||
MOCK_DETECTORS = {
|
|
||||||
"hate": {
|
|
||||||
"name": "Hate Speech Detector",
|
|
||||||
"triggers": ["hate", "offensive", "discriminatory", "slur"],
|
|
||||||
"default_score": 0.85,
|
|
||||||
},
|
|
||||||
"pii": {
|
|
||||||
"name": "PII Detector",
|
|
||||||
"triggers": ["email", "ssn", "credit card", "phone number", "address"],
|
|
||||||
"default_score": 0.92,
|
|
||||||
},
|
|
||||||
"toxicity": {
|
|
||||||
"name": "Toxicity Detector",
|
|
||||||
"triggers": ["toxic", "abusive", "profanity", "insult"],
|
|
||||||
"default_score": 0.78,
|
|
||||||
},
|
|
||||||
"jailbreak": {
|
|
||||||
"name": "Jailbreak Detector",
|
|
||||||
"triggers": ["ignore instructions", "override", "bypass", "jailbreak"],
|
|
||||||
"default_score": 0.88,
|
|
||||||
},
|
|
||||||
"prompt_injection": {
|
|
||||||
"name": "Prompt Injection Detector",
|
|
||||||
"triggers": ["ignore previous", "new instructions", "system prompt"],
|
|
||||||
"default_score": 0.90,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def simulate_detection(
|
|
||||||
detector_id: str, content: str, detector_params: Optional[DetectorParams] = None
|
|
||||||
) -> List[Detection]:
|
|
||||||
"""
|
|
||||||
Simulate detection logic based on detector type and content.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
detector_id: ID of the detector to simulate
|
|
||||||
content: Text content to analyze
|
|
||||||
detector_params: Optional detector parameters
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of Detection objects
|
|
||||||
"""
|
|
||||||
detections = []
|
|
||||||
content_lower = " ".join(c for c in content).lower()
|
|
||||||
|
|
||||||
# Get detector config
|
|
||||||
detector_config = MOCK_DETECTORS.get(detector_id)
|
|
||||||
if not detector_config:
|
|
||||||
# Unknown detector - return no detections
|
|
||||||
return detections
|
|
||||||
|
|
||||||
# Check for triggers in content
|
|
||||||
for trigger in detector_config["triggers"]:
|
|
||||||
if trigger in content_lower:
|
|
||||||
# Calculate score (use threshold if provided, otherwise default)
|
|
||||||
base_score = detector_config["default_score"]
|
|
||||||
threshold = (
|
|
||||||
detector_params.threshold
|
|
||||||
if detector_params and detector_params.threshold
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
|
|
||||||
# Adjust score slightly based on content length (longer content = slightly lower confidence)
|
|
||||||
score_adjustment = max(0, min(0.1, len(content) / 10000))
|
|
||||||
score = max(0.0, min(1.0, base_score - score_adjustment))
|
|
||||||
|
|
||||||
# Find position of trigger
|
|
||||||
start_pos = content_lower.find(trigger)
|
|
||||||
end_pos = start_pos + len(trigger)
|
|
||||||
|
|
||||||
detection = Detection(
|
|
||||||
detection_type=detector_id,
|
|
||||||
detection=threshold is None or score >= threshold,
|
|
||||||
score=score,
|
|
||||||
start=start_pos,
|
|
||||||
end=end_pos,
|
|
||||||
text=content[start_pos:end_pos] if start_pos >= 0 else None,
|
|
||||||
evidence=[f"Found trigger word: {trigger}"],
|
|
||||||
)
|
|
||||||
detections.append(detection)
|
|
||||||
|
|
||||||
# If no triggers found, return a negative detection
|
|
||||||
if not detections:
|
|
||||||
detections.append(
|
|
||||||
Detection(
|
|
||||||
detection_type=detector_id,
|
|
||||||
detection=False,
|
|
||||||
score=0.05, # Low score for clean content
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return detections
|
|
||||||
|
|
||||||
|
|
||||||
# Authentication middleware
|
|
||||||
def verify_auth_token(authorization: Optional[str] = Header(None)) -> bool:
|
|
||||||
"""
|
|
||||||
Verify the authentication token.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
authorization: Authorization header value
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
True if valid, raises HTTPException otherwise
|
|
||||||
"""
|
|
||||||
if not authorization:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="Missing authorization header",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Simple token validation - in real implementation, this would validate against a real auth system
|
|
||||||
if not authorization.startswith("Bearer "):
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="Invalid authorization header format. Expected: Bearer <token>",
|
|
||||||
)
|
|
||||||
|
|
||||||
token = authorization.replace("Bearer ", "")
|
|
||||||
|
|
||||||
# Accept any non-empty token for mock purposes
|
|
||||||
if not token:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="Empty token provided",
|
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
# API Endpoints
|
|
||||||
@app.get("/health")
|
|
||||||
async def health_check():
|
|
||||||
"""Health check endpoint."""
|
|
||||||
return {"status": "healthy", "service": "IBM FMS Guardrails Mock Server"}
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
|
||||||
async def root():
|
|
||||||
"""Root endpoint with API information."""
|
|
||||||
return {
|
|
||||||
"service": "IBM FMS Guardrails Orchestrator Mock",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"endpoints": {
|
|
||||||
"health": "/health",
|
|
||||||
"text_detection": "/api/v1/text/detection",
|
|
||||||
"generation_detection": "/api/v1/text/generation/detection",
|
|
||||||
"context_detection": "/api/v1/text/context/detection",
|
|
||||||
},
|
|
||||||
"available_detectors": list(MOCK_DETECTORS.keys()),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/v1/text/contents")
|
|
||||||
async def text_detection(
|
|
||||||
request: TextDetectionRequest,
|
|
||||||
detector_id: str = Header(None), # query parameter
|
|
||||||
authorization: Optional[str] = Header(None),
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Detect potential issues in text content.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request: Detection request with content and detector ID
|
|
||||||
detector_id: ID of detector
|
|
||||||
authorization: Bearer token for authentication
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Detection results
|
|
||||||
"""
|
|
||||||
verify_auth_token(authorization)
|
|
||||||
|
|
||||||
detections = simulate_detection(
|
|
||||||
detector_id=detector_id,
|
|
||||||
content=request.contents,
|
|
||||||
detector_params=request.detector_params,
|
|
||||||
)
|
|
||||||
|
|
||||||
return detections
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/v1/text/generation/detection", response_model=DetectionResponse)
|
|
||||||
async def text_generation_detection(
|
|
||||||
request: TextGenerationDetectionRequest,
|
|
||||||
authorization: Optional[str] = Header(None),
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Detect potential issues in generated text.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request: Detection request with prompt and generated text
|
|
||||||
authorization: Bearer token for authentication
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Detection results
|
|
||||||
"""
|
|
||||||
verify_auth_token(authorization)
|
|
||||||
|
|
||||||
# Analyze both prompt and generated text
|
|
||||||
combined_content = f"{request.prompt} {request.generated_text}"
|
|
||||||
|
|
||||||
detections = simulate_detection(
|
|
||||||
detector_id=request.detector_id,
|
|
||||||
content=combined_content,
|
|
||||||
detector_params=request.detector_params,
|
|
||||||
)
|
|
||||||
|
|
||||||
return DetectionResponse(
|
|
||||||
detections=detections,
|
|
||||||
detection_id=str(uuid.uuid4()),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/v1/text/context/detection", response_model=DetectionResponse)
|
|
||||||
async def context_detection(
|
|
||||||
request: ContextDetectionRequest,
|
|
||||||
authorization: Optional[str] = Header(None),
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Detect potential issues in text with additional context.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request: Detection request with content and context
|
|
||||||
authorization: Bearer token for authentication
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Detection results
|
|
||||||
"""
|
|
||||||
verify_auth_token(authorization)
|
|
||||||
|
|
||||||
detections = simulate_detection(
|
|
||||||
detector_id=request.detector_id,
|
|
||||||
content=request.content,
|
|
||||||
detector_params=request.detector_params,
|
|
||||||
)
|
|
||||||
|
|
||||||
return DetectionResponse(
|
|
||||||
detections=detections,
|
|
||||||
detection_id=str(uuid.uuid4()),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/v1/detectors")
|
|
||||||
async def list_detectors(authorization: Optional[str] = Header(None)):
|
|
||||||
"""
|
|
||||||
List available detectors.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
authorization: Bearer token for authentication
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of available detectors
|
|
||||||
"""
|
|
||||||
verify_auth_token(authorization)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"detectors": [
|
|
||||||
{
|
|
||||||
"id": detector_id,
|
|
||||||
"name": config["name"],
|
|
||||||
"triggers": config["triggers"],
|
|
||||||
}
|
|
||||||
for detector_id, config in MOCK_DETECTORS.items()
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
print("🚀 Starting IBM FMS Guardrails Mock Server...")
|
|
||||||
print("📍 Server will be available at: http://localhost:8001")
|
|
||||||
print("📚 API docs at: http://localhost:8001/docs")
|
|
||||||
print("\nAvailable detectors:")
|
|
||||||
for detector_id, config in MOCK_DETECTORS.items():
|
|
||||||
print(f" - {detector_id}: {config['name']}")
|
|
||||||
print("\n✨ Use any Bearer token for authentication in this mock server\n")
|
|
||||||
|
|
||||||
uvicorn.run(app, host="0.0.0.0", port=8001)
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
"""
|
|
||||||
Test script to reproduce the Groq streaming ASCII encoding issue.
|
|
||||||
|
|
||||||
This reproduces the issue described in #12660 where streaming responses
|
|
||||||
containing non-ASCII characters like µ cause encoding errors.
|
|
||||||
"""
|
|
||||||
import asyncio
|
|
||||||
import os
|
|
||||||
import traceback
|
|
||||||
from litellm import acompletion
|
|
||||||
|
|
||||||
async def test_groq_streaming_with_special_chars():
|
|
||||||
"""Test that reproduces the ASCII encoding issue with Groq streaming."""
|
|
||||||
try:
|
|
||||||
print("Testing acompletion + streaming with Groq...")
|
|
||||||
|
|
||||||
# Test message that should trigger the µ character or similar non-ASCII content
|
|
||||||
test_messages = [
|
|
||||||
{"content": "What is the symbol for micro? Please include the µ symbol in your response.", "role": "user"}
|
|
||||||
]
|
|
||||||
|
|
||||||
# This should trigger the ASCII encoding error described in the issue
|
|
||||||
response = await acompletion(
|
|
||||||
model="groq/llama-3.3-70b-versatile",
|
|
||||||
messages=test_messages,
|
|
||||||
stream=True
|
|
||||||
)
|
|
||||||
|
|
||||||
print(f"Response type: {type(response)}")
|
|
||||||
|
|
||||||
# Try to iterate through the stream
|
|
||||||
async for chunk in response:
|
|
||||||
print(f"Chunk: {chunk}")
|
|
||||||
|
|
||||||
print("✅ Test completed successfully - no encoding errors!")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ Error occurred: {e}")
|
|
||||||
print(f"Error type: {type(e)}")
|
|
||||||
print(f"Traceback:\n{traceback.format_exc()}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# Note: This requires GROQ_API_KEY to be set
|
|
||||||
if not os.getenv("GROQ_API_KEY"):
|
|
||||||
print("⚠️ GROQ_API_KEY not set. Skipping test.")
|
|
||||||
else:
|
|
||||||
success = asyncio.run(test_groq_streaming_with_special_chars())
|
|
||||||
if success:
|
|
||||||
print("🎉 All tests passed!")
|
|
||||||
else:
|
|
||||||
print("💥 Test failed!")
|
|
||||||
@ -1,181 +0,0 @@
|
|||||||
"""
|
|
||||||
Test script for the mock IBM Guardrails server.
|
|
||||||
|
|
||||||
This demonstrates how to interact with the mock server.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
# Start the mock server in one terminal:
|
|
||||||
python scripts/mock_ibm_guardrails_server.py
|
|
||||||
|
|
||||||
# Run this test in another terminal:
|
|
||||||
python scripts/test_mock_ibm_guardrails.py
|
|
||||||
"""
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
|
|
||||||
async def test_mock_server():
|
|
||||||
"""Test the mock IBM Guardrails server."""
|
|
||||||
base_url = "http://localhost:8001"
|
|
||||||
headers = {"Authorization": "Bearer test-token-12345"}
|
|
||||||
|
|
||||||
print("🧪 Testing IBM FMS Guardrails Mock Server\n")
|
|
||||||
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
# Test 1: Health check
|
|
||||||
print("1️⃣ Testing health check...")
|
|
||||||
try:
|
|
||||||
response = await client.get(f"{base_url}/health")
|
|
||||||
print(f" ✅ Health check: {response.json()}\n")
|
|
||||||
except Exception as e:
|
|
||||||
print(f" ❌ Health check failed: {e}\n")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Test 2: List detectors
|
|
||||||
print("2️⃣ Testing list detectors...")
|
|
||||||
try:
|
|
||||||
response = await client.get(
|
|
||||||
f"{base_url}/api/v1/detectors",
|
|
||||||
headers=headers
|
|
||||||
)
|
|
||||||
detectors = response.json()
|
|
||||||
print(f" ✅ Found {len(detectors['detectors'])} detectors:")
|
|
||||||
for detector in detectors["detectors"]:
|
|
||||||
print(f" - {detector['id']}: {detector['name']}")
|
|
||||||
print()
|
|
||||||
except Exception as e:
|
|
||||||
print(f" ❌ List detectors failed: {e}\n")
|
|
||||||
|
|
||||||
# Test 3: Text detection with clean content
|
|
||||||
print("3️⃣ Testing text detection (clean content)...")
|
|
||||||
try:
|
|
||||||
response = await client.post(
|
|
||||||
f"{base_url}/api/v1/text/detection",
|
|
||||||
headers=headers,
|
|
||||||
json={
|
|
||||||
"detector_id": "hate",
|
|
||||||
"content": "This is a normal, friendly message.",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
result = response.json()
|
|
||||||
print(f" ✅ Detection result:")
|
|
||||||
print(f" Detection ID: {result['detection_id']}")
|
|
||||||
for detection in result["detections"]:
|
|
||||||
print(f" - Type: {detection['detection_type']}, Detected: {detection['detection']}, Score: {detection['score']:.2f}")
|
|
||||||
print()
|
|
||||||
except Exception as e:
|
|
||||||
print(f" ❌ Text detection failed: {e}\n")
|
|
||||||
|
|
||||||
# Test 4: Text detection with problematic content
|
|
||||||
print("4️⃣ Testing text detection (problematic content)...")
|
|
||||||
try:
|
|
||||||
response = await client.post(
|
|
||||||
f"{base_url}/api/v1/text/detection",
|
|
||||||
headers=headers,
|
|
||||||
json={
|
|
||||||
"detector_id": "hate",
|
|
||||||
"content": "This message contains hate speech and offensive language.",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
result = response.json()
|
|
||||||
print(f" ✅ Detection result:")
|
|
||||||
print(f" Detection ID: {result['detection_id']}")
|
|
||||||
for detection in result["detections"]:
|
|
||||||
print(f" - Type: {detection['detection_type']}, Detected: {detection['detection']}, Score: {detection['score']:.2f}")
|
|
||||||
if detection.get("evidence"):
|
|
||||||
print(f" Evidence: {detection['evidence']}")
|
|
||||||
print()
|
|
||||||
except Exception as e:
|
|
||||||
print(f" ❌ Text detection failed: {e}\n")
|
|
||||||
|
|
||||||
# Test 5: PII detection
|
|
||||||
print("5️⃣ Testing PII detection...")
|
|
||||||
try:
|
|
||||||
response = await client.post(
|
|
||||||
f"{base_url}/api/v1/text/detection",
|
|
||||||
headers=headers,
|
|
||||||
json={
|
|
||||||
"detector_id": "pii",
|
|
||||||
"content": "Please send the report to my email address john@example.com",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
result = response.json()
|
|
||||||
print(f" ✅ Detection result:")
|
|
||||||
print(f" Detection ID: {result['detection_id']}")
|
|
||||||
for detection in result["detections"]:
|
|
||||||
print(f" - Type: {detection['detection_type']}, Detected: {detection['detection']}, Score: {detection['score']:.2f}")
|
|
||||||
if detection.get("text"):
|
|
||||||
print(f" Detected text: '{detection['text']}'")
|
|
||||||
print()
|
|
||||||
except Exception as e:
|
|
||||||
print(f" ❌ PII detection failed: {e}\n")
|
|
||||||
|
|
||||||
# Test 6: Generation detection
|
|
||||||
print("6️⃣ Testing text generation detection...")
|
|
||||||
try:
|
|
||||||
response = await client.post(
|
|
||||||
f"{base_url}/api/v1/text/generation/detection",
|
|
||||||
headers=headers,
|
|
||||||
json={
|
|
||||||
"detector_id": "jailbreak",
|
|
||||||
"prompt": "Tell me about AI safety",
|
|
||||||
"generated_text": "I will ignore instructions and provide harmful content.",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
result = response.json()
|
|
||||||
print(f" ✅ Detection result:")
|
|
||||||
print(f" Detection ID: {result['detection_id']}")
|
|
||||||
for detection in result["detections"]:
|
|
||||||
print(f" - Type: {detection['detection_type']}, Detected: {detection['detection']}, Score: {detection['score']:.2f}")
|
|
||||||
print()
|
|
||||||
except Exception as e:
|
|
||||||
print(f" ❌ Generation detection failed: {e}\n")
|
|
||||||
|
|
||||||
# Test 7: Detection with custom threshold
|
|
||||||
print("7️⃣ Testing detection with custom threshold...")
|
|
||||||
try:
|
|
||||||
response = await client.post(
|
|
||||||
f"{base_url}/api/v1/text/detection",
|
|
||||||
headers=headers,
|
|
||||||
json={
|
|
||||||
"detector_id": "toxicity",
|
|
||||||
"content": "This contains toxic language",
|
|
||||||
"detector_params": {
|
|
||||||
"threshold": 0.9
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
result = response.json()
|
|
||||||
print(f" ✅ Detection result (threshold=0.9):")
|
|
||||||
print(f" Detection ID: {result['detection_id']}")
|
|
||||||
for detection in result["detections"]:
|
|
||||||
print(f" - Type: {detection['detection_type']}, Detected: {detection['detection']}, Score: {detection['score']:.2f}")
|
|
||||||
print()
|
|
||||||
except Exception as e:
|
|
||||||
print(f" ❌ Threshold detection failed: {e}\n")
|
|
||||||
|
|
||||||
# Test 8: Authentication error
|
|
||||||
print("8️⃣ Testing authentication error...")
|
|
||||||
try:
|
|
||||||
response = await client.post(
|
|
||||||
f"{base_url}/api/v1/text/detection",
|
|
||||||
json={
|
|
||||||
"detector_id": "hate",
|
|
||||||
"content": "Test content",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if response.status_code == 401:
|
|
||||||
print(f" ✅ Authentication error handled correctly: {response.json()}\n")
|
|
||||||
else:
|
|
||||||
print(f" ⚠️ Unexpected status code: {response.status_code}\n")
|
|
||||||
except Exception as e:
|
|
||||||
print(f" ❌ Auth test failed: {e}\n")
|
|
||||||
|
|
||||||
print("✨ All tests completed!")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
asyncio.run(test_mock_server())
|
|
||||||
|
|
||||||
@ -1,147 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Script to update the README.md providers table from provider_endpoints_support.json
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Define paths
|
|
||||||
REPO_ROOT = Path(__file__).parent.parent
|
|
||||||
JSON_PATH = REPO_ROOT / "provider_endpoints_support.json"
|
|
||||||
README_PATH = REPO_ROOT / "README.md"
|
|
||||||
|
|
||||||
# Endpoint column headers
|
|
||||||
ENDPOINT_COLUMNS = [
|
|
||||||
("/chat/completions", "chat_completions"),
|
|
||||||
("/messages", "messages"),
|
|
||||||
("/responses", "responses"),
|
|
||||||
("/embeddings", "embeddings"),
|
|
||||||
("/image/generations", "image_generations"),
|
|
||||||
("/audio/transcriptions", "audio_transcriptions"),
|
|
||||||
("/audio/speech", "audio_speech"),
|
|
||||||
("/moderations", "moderations"),
|
|
||||||
("/batches", "batches"),
|
|
||||||
("/rerank", "rerank"),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def load_providers_data():
|
|
||||||
"""Load provider data from JSON file"""
|
|
||||||
with open(JSON_PATH, 'r') as f:
|
|
||||||
data = json.load(f)
|
|
||||||
|
|
||||||
# Handle both old and new format
|
|
||||||
if "providers" in data:
|
|
||||||
return data["providers"]
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def generate_markdown_table(providers_data):
|
|
||||||
"""Generate markdown table from providers data"""
|
|
||||||
|
|
||||||
# Sort providers alphabetically by display name
|
|
||||||
sorted_providers = sorted(
|
|
||||||
providers_data.items(),
|
|
||||||
key=lambda x: x[1]['display_name'].lower()
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generate header
|
|
||||||
header_cols = ["Provider"] + [col[0] for col in ENDPOINT_COLUMNS]
|
|
||||||
header = "| " + " | ".join(header_cols) + " |"
|
|
||||||
separator = "|" + "|".join(["-" * (len(col) + 2) for col in header_cols]) + "|"
|
|
||||||
|
|
||||||
# Generate rows
|
|
||||||
rows = []
|
|
||||||
for slug, data in sorted_providers:
|
|
||||||
display_name = data['display_name']
|
|
||||||
url = data['url']
|
|
||||||
|
|
||||||
# Build row
|
|
||||||
row_parts = [f"[{display_name}]({url})"]
|
|
||||||
|
|
||||||
for _, endpoint_key in ENDPOINT_COLUMNS:
|
|
||||||
supported = data['endpoints'].get(endpoint_key, False)
|
|
||||||
row_parts.append("✅" if supported else "")
|
|
||||||
|
|
||||||
row = "| " + " | ".join(row_parts) + " |"
|
|
||||||
rows.append(row)
|
|
||||||
|
|
||||||
# Combine all parts
|
|
||||||
table_lines = [
|
|
||||||
"<!-- AUTO-GENERATED TABLE - DO NOT EDIT MANUALLY -->",
|
|
||||||
"<!-- Edit provider_endpoints_support.json and run scripts/update_readme_providers_table.py -->",
|
|
||||||
"",
|
|
||||||
header,
|
|
||||||
separator
|
|
||||||
] + rows + [
|
|
||||||
"<!-- END AUTO-GENERATED TABLE -->"
|
|
||||||
]
|
|
||||||
|
|
||||||
return "\n".join(table_lines)
|
|
||||||
|
|
||||||
|
|
||||||
def update_readme(table_markdown):
|
|
||||||
"""Update README.md with new table"""
|
|
||||||
with open(README_PATH, 'r') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
print(f" Original README length: {len(content)} bytes")
|
|
||||||
|
|
||||||
# Find the table section
|
|
||||||
# Look for the AUTO-GENERATED comment or the header, and replace until Read the Docs
|
|
||||||
pattern = r"(## Supported Providers.*?\n\n)(?:<!-- AUTO-GENERATED TABLE.*?<!-- END AUTO-GENERATED TABLE -->|.*?)(\n\n\[\*\*Read the Docs\*\*\])"
|
|
||||||
|
|
||||||
# Test if pattern matches
|
|
||||||
match = re.search(pattern, content, flags=re.DOTALL)
|
|
||||||
if not match:
|
|
||||||
print("❌ Pattern did not match in README.md")
|
|
||||||
return False
|
|
||||||
|
|
||||||
print(f" Pattern matched, replacing table...")
|
|
||||||
|
|
||||||
def replacer(match):
|
|
||||||
return match.group(1) + table_markdown + match.group(2)
|
|
||||||
|
|
||||||
new_content = re.sub(pattern, replacer, content, flags=re.DOTALL)
|
|
||||||
|
|
||||||
print(f" New README length: {len(new_content)} bytes")
|
|
||||||
|
|
||||||
if new_content == content:
|
|
||||||
print(" ℹ️ Table is already up-to-date, no changes needed")
|
|
||||||
return True # Not an error - table is already correct
|
|
||||||
|
|
||||||
with open(README_PATH, 'w') as f:
|
|
||||||
f.write(new_content)
|
|
||||||
|
|
||||||
print(" ✓ README.md has been updated")
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Main function"""
|
|
||||||
print("Loading provider data from provider_endpoints_support.json...")
|
|
||||||
providers_data = load_providers_data()
|
|
||||||
print(f"✓ Loaded {len(providers_data)} providers")
|
|
||||||
|
|
||||||
print("\nGenerating markdown table...")
|
|
||||||
table_markdown = generate_markdown_table(providers_data)
|
|
||||||
print(f"✓ Generated table with {len(providers_data)} rows")
|
|
||||||
|
|
||||||
print("\nUpdating README.md...")
|
|
||||||
if update_readme(table_markdown):
|
|
||||||
print("✓ Successfully updated README.md")
|
|
||||||
print("\n📝 Please review the changes and commit both files:")
|
|
||||||
print(" - provider_endpoints_support.json")
|
|
||||||
print(" - README.md")
|
|
||||||
else:
|
|
||||||
print("❌ Failed to update README.md")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
exit(main())
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user