0ef5fcb1c5
Security / Dependency audit (pip-audit) (push) Has been cancelled
Security / CodeQL (javascript-typescript) (push) Has been cancelled
Security / CodeQL (python) (push) Has been cancelled
Security / Secret scan (gitleaks) (push) Has been cancelled
rust / test (ubuntu) (push) Has been cancelled
rust / simulator e2e (macos-latest) (push) Has been cancelled
rust / simulator e2e (ubuntu-latest) (push) Has been cancelled
rust / simulator e2e (windows-latest) (push) Has been cancelled
rust / wheels (aarch64-apple-darwin) (push) Has been cancelled
rust / wheels (x86_64-unknown-linux-gnu) (push) Has been cancelled
rust / wheels (x86_64-apple-darwin) (push) Has been cancelled
rust / audit (push) Has been cancelled
rust / parity (nightly, allowed to fail during Phase 0) (push) Has been cancelled
CI / commitlint (push) Has been skipped
Dev Containers / validate (.devcontainer/devcontainer.json, default) (push) Failing after 0s
Dev Containers / validate (.devcontainer/memory-stack/devcontainer.json, memory-stack) (push) Failing after 0s
Dev Containers / validate-worktree (push) Failing after 0s
CI / changes (push) Failing after 4s
Deploy Documentation / validate (push) Has been skipped
Deploy Documentation / deploy (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, claude) (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, codex) (push) Failing after 1s
Install Native E2E / install-native (ubuntu-latest) (push) Failing after 1s
OpenCode Plugin / typecheck + build + test (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, copilot) (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
Wrap E2E / docker-wrap-e2e (push) Failing after 1s
Wrap Native E2E / wrap-native (ubuntu-latest) (push) Failing after 1s
Init E2E / docker-init-e2e (push) Failing after 4s
Merge Conflicts / merge-conflicts (push) Failing after 4s
CI / lint (push) Has been cancelled
CI / build-wheel (push) Has been cancelled
CI / build-wheel-windows (push) Has been cancelled
CI / prefetch-model (push) Has been cancelled
CI / test-dashboard-ui (push) Has been cancelled
CI / test (1) (push) Has been cancelled
CI / test (2) (push) Has been cancelled
CI / test (3) (push) Has been cancelled
CI / test (4) (push) Has been cancelled
CI / test-extras (push) Has been cancelled
CI / test-agno (push) Has been cancelled
CI / build (push) Has been cancelled
CI / workflow-validation (push) Has been cancelled
CI / docker-native-e2e (push) Has been cancelled
CI / windows-native-wrapper (push) Has been cancelled
CI / macos-native-wrapper (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / promote-latest (push) Has been cancelled
Init Native E2E / init-native (macos-latest, claude) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, codex) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, copilot) (push) Has been cancelled
Install Native E2E / install-native (macos-latest) (push) Has been cancelled
Wrap Native E2E / wrap-native (macos-latest) (push) Has been cancelled
1219 lines
42 KiB
Python
1219 lines
42 KiB
Python
"""Real-world evaluation tests for UniversalCompressor.
|
|
|
|
These tests simulate agent tool outputs with realistic content:
|
|
- JSON API responses (search results, user data)
|
|
- Code file reads (Python, JavaScript)
|
|
- Log outputs (application logs)
|
|
- Mixed content scenarios (agent processing multiple tools)
|
|
|
|
Evaluation metrics:
|
|
1. Structure Preservation: Keys, signatures, templates remain visible
|
|
2. Compression Ratio: Actual token reduction achieved
|
|
3. Retrievability: Can LLM identify what data exists for CCR retrieval?
|
|
"""
|
|
|
|
import json
|
|
from dataclasses import dataclass
|
|
|
|
import pytest
|
|
|
|
from headroom.compression.detector import ContentType
|
|
from headroom.compression.universal import (
|
|
CompressionResult,
|
|
UniversalCompressor,
|
|
UniversalCompressorConfig,
|
|
)
|
|
|
|
# =============================================================================
|
|
# Realistic Test Fixtures - Simulated Tool Outputs
|
|
# =============================================================================
|
|
|
|
GITHUB_SEARCH_RESPONSE = json.dumps(
|
|
{
|
|
"total_count": 3,
|
|
"incomplete_results": False,
|
|
"items": [
|
|
{
|
|
"id": 12345678,
|
|
"node_id": "MDEwOlJlcG9zaXRvcnkxMjM0NTY3OA==",
|
|
"name": "headroom",
|
|
"full_name": "anthropic/headroom",
|
|
"private": False,
|
|
"owner": {
|
|
"login": "anthropic",
|
|
"id": 98765,
|
|
"avatar_url": "https://avatars.githubusercontent.com/u/98765?v=4",
|
|
"type": "Organization",
|
|
},
|
|
"description": "Context optimization layer for LLM applications with intelligent compression and caching",
|
|
"fork": False,
|
|
"url": "https://api.github.com/repos/anthropic/headroom",
|
|
"created_at": "2024-01-15T10:30:00Z",
|
|
"updated_at": "2024-06-20T15:45:00Z",
|
|
"pushed_at": "2024-06-20T14:30:00Z",
|
|
"stargazers_count": 1250,
|
|
"watchers_count": 1250,
|
|
"forks_count": 89,
|
|
"language": "Python",
|
|
"topics": ["llm", "compression", "caching", "context-management"],
|
|
"default_branch": "main",
|
|
},
|
|
{
|
|
"id": 23456789,
|
|
"node_id": "MDEwOlJlcG9zaXRvcnkyMzQ1Njc4OQ==",
|
|
"name": "llm-cache",
|
|
"full_name": "openai/llm-cache",
|
|
"private": False,
|
|
"owner": {
|
|
"login": "openai",
|
|
"id": 87654,
|
|
"avatar_url": "https://avatars.githubusercontent.com/u/87654?v=4",
|
|
"type": "Organization",
|
|
},
|
|
"description": "High-performance caching layer for large language model responses with semantic similarity matching",
|
|
"fork": False,
|
|
"url": "https://api.github.com/repos/openai/llm-cache",
|
|
"created_at": "2023-09-01T08:00:00Z",
|
|
"updated_at": "2024-05-15T12:00:00Z",
|
|
"pushed_at": "2024-05-15T11:30:00Z",
|
|
"stargazers_count": 890,
|
|
"watchers_count": 890,
|
|
"forks_count": 67,
|
|
"language": "Python",
|
|
"topics": ["caching", "llm", "semantic-search"],
|
|
"default_branch": "main",
|
|
},
|
|
{
|
|
"id": 34567890,
|
|
"node_id": "MDEwOlJlcG9zaXRvcnkzNDU2Nzg5MA==",
|
|
"name": "prompt-optimizer",
|
|
"full_name": "google/prompt-optimizer",
|
|
"private": False,
|
|
"owner": {
|
|
"login": "google",
|
|
"id": 76543,
|
|
"avatar_url": "https://avatars.githubusercontent.com/u/76543?v=4",
|
|
"type": "Organization",
|
|
},
|
|
"description": "Automatic prompt optimization using evolutionary algorithms and reinforcement learning from human feedback",
|
|
"fork": False,
|
|
"url": "https://api.github.com/repos/google/prompt-optimizer",
|
|
"created_at": "2024-02-20T09:15:00Z",
|
|
"updated_at": "2024-06-18T16:20:00Z",
|
|
"pushed_at": "2024-06-18T16:00:00Z",
|
|
"stargazers_count": 2100,
|
|
"watchers_count": 2100,
|
|
"forks_count": 156,
|
|
"language": "Python",
|
|
"topics": ["prompt-engineering", "optimization", "rlhf"],
|
|
"default_branch": "main",
|
|
},
|
|
],
|
|
},
|
|
indent=2,
|
|
)
|
|
|
|
PYTHON_FILE_CONTENT = '''"""Authentication middleware for FastAPI applications.
|
|
|
|
This module provides JWT-based authentication with role-based access control.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from datetime import datetime, timezone, timedelta
|
|
from typing import Optional, List, Any
|
|
|
|
import jwt
|
|
from fastapi import HTTPException, Security, Depends
|
|
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
from pydantic import BaseModel, Field
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Configuration constants
|
|
JWT_SECRET = "your-secret-key-here-replace-in-production"
|
|
JWT_ALGORITHM = "HS256"
|
|
TOKEN_EXPIRE_MINUTES = 60
|
|
|
|
|
|
class TokenPayload(BaseModel):
|
|
"""JWT token payload structure."""
|
|
|
|
sub: str = Field(..., description="Subject (user ID)")
|
|
exp: datetime = Field(..., description="Expiration time")
|
|
roles: List[str] = Field(default_factory=list, description="User roles")
|
|
permissions: List[str] = Field(default_factory=list, description="Specific permissions")
|
|
|
|
|
|
class UserContext(BaseModel):
|
|
"""Authenticated user context passed to endpoints."""
|
|
|
|
user_id: str
|
|
email: Optional[str] = None
|
|
roles: List[str] = Field(default_factory=list)
|
|
permissions: List[str] = Field(default_factory=list)
|
|
is_admin: bool = False
|
|
|
|
|
|
security = HTTPBearer()
|
|
|
|
|
|
def create_access_token(
|
|
user_id: str,
|
|
roles: List[str] = None,
|
|
permissions: List[str] = None,
|
|
expires_delta: Optional[timedelta] = None
|
|
) -> str:
|
|
"""Create a new JWT access token.
|
|
|
|
Args:
|
|
user_id: The user's unique identifier.
|
|
roles: List of role names assigned to the user.
|
|
permissions: List of specific permissions.
|
|
expires_delta: Custom expiration time. Defaults to TOKEN_EXPIRE_MINUTES.
|
|
|
|
Returns:
|
|
Encoded JWT token string.
|
|
|
|
Example:
|
|
>>> token = create_access_token("user_123", roles=["admin", "editor"])
|
|
>>> print(token) # eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
"""
|
|
if expires_delta:
|
|
expire = datetime.now(timezone.utc).replace(tzinfo=None) + expires_delta
|
|
else:
|
|
expire = datetime.now(timezone.utc).replace(tzinfo=None) + timedelta(minutes=TOKEN_EXPIRE_MINUTES)
|
|
|
|
payload = {
|
|
"sub": user_id,
|
|
"exp": expire,
|
|
"roles": roles or [],
|
|
"permissions": permissions or [],
|
|
"iat": datetime.now(timezone.utc).replace(tzinfo=None),
|
|
}
|
|
|
|
encoded_jwt = jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)
|
|
logger.info(f"Created access token for user {user_id}, expires at {expire}")
|
|
return encoded_jwt
|
|
|
|
|
|
def decode_token(token: str) -> TokenPayload:
|
|
"""Decode and validate a JWT token.
|
|
|
|
Args:
|
|
token: The JWT token string to decode.
|
|
|
|
Returns:
|
|
TokenPayload with decoded claims.
|
|
|
|
Raises:
|
|
HTTPException: If token is invalid, expired, or malformed.
|
|
"""
|
|
try:
|
|
payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
|
|
return TokenPayload(**payload)
|
|
except jwt.ExpiredSignatureError:
|
|
logger.warning("Token has expired")
|
|
raise HTTPException(status_code=401, detail="Token has expired")
|
|
except jwt.JWTError as e:
|
|
logger.error(f"Token validation failed: {e}")
|
|
raise HTTPException(status_code=401, detail="Could not validate credentials")
|
|
|
|
|
|
async def get_current_user(
|
|
credentials: HTTPAuthorizationCredentials = Security(security)
|
|
) -> UserContext:
|
|
"""FastAPI dependency to get the current authenticated user.
|
|
|
|
Args:
|
|
credentials: HTTP Bearer token from request header.
|
|
|
|
Returns:
|
|
UserContext with user information.
|
|
|
|
Raises:
|
|
HTTPException: If authentication fails.
|
|
"""
|
|
token_data = decode_token(credentials.credentials)
|
|
|
|
return UserContext(
|
|
user_id=token_data.sub,
|
|
roles=token_data.roles,
|
|
permissions=token_data.permissions,
|
|
is_admin="admin" in token_data.roles,
|
|
)
|
|
|
|
|
|
def require_roles(*required_roles: str):
|
|
"""Decorator factory for role-based access control.
|
|
|
|
Args:
|
|
*required_roles: One or more role names required to access the endpoint.
|
|
|
|
Returns:
|
|
FastAPI dependency that validates user has required roles.
|
|
|
|
Example:
|
|
>>> @app.get("/admin/users")
|
|
>>> async def list_users(user: UserContext = Depends(require_roles("admin"))):
|
|
>>> return {"users": [...]}
|
|
"""
|
|
async def role_checker(user: UserContext = Depends(get_current_user)) -> UserContext:
|
|
if not any(role in user.roles for role in required_roles):
|
|
logger.warning(f"User {user.user_id} lacks required roles: {required_roles}")
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail=f"Requires one of roles: {', '.join(required_roles)}"
|
|
)
|
|
return user
|
|
return role_checker
|
|
|
|
|
|
def require_permissions(*required_permissions: str):
|
|
"""Decorator factory for permission-based access control.
|
|
|
|
Args:
|
|
*required_permissions: Permissions required to access the endpoint.
|
|
|
|
Returns:
|
|
FastAPI dependency that validates user has required permissions.
|
|
"""
|
|
async def permission_checker(user: UserContext = Depends(get_current_user)) -> UserContext:
|
|
missing = set(required_permissions) - set(user.permissions)
|
|
if missing and not user.is_admin: # Admins bypass permission checks
|
|
logger.warning(f"User {user.user_id} missing permissions: {missing}")
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail=f"Missing permissions: {', '.join(missing)}"
|
|
)
|
|
return user
|
|
return permission_checker
|
|
|
|
|
|
class RateLimiter:
|
|
"""Simple in-memory rate limiter for API endpoints.
|
|
|
|
Attributes:
|
|
requests_per_minute: Maximum requests allowed per minute per user.
|
|
window_size: Time window in seconds for rate limiting.
|
|
"""
|
|
|
|
def __init__(self, requests_per_minute: int = 60, window_size: int = 60):
|
|
self.requests_per_minute = requests_per_minute
|
|
self.window_size = window_size
|
|
self._requests: dict[str, list[datetime]] = {}
|
|
|
|
def is_allowed(self, user_id: str) -> bool:
|
|
"""Check if a request from this user is allowed.
|
|
|
|
Args:
|
|
user_id: The user making the request.
|
|
|
|
Returns:
|
|
True if request is allowed, False if rate limited.
|
|
"""
|
|
now = datetime.now(timezone.utc).replace(tzinfo=None)
|
|
cutoff = now - timedelta(seconds=self.window_size)
|
|
|
|
# Clean old entries
|
|
if user_id in self._requests:
|
|
self._requests[user_id] = [
|
|
t for t in self._requests[user_id] if t > cutoff
|
|
]
|
|
else:
|
|
self._requests[user_id] = []
|
|
|
|
if len(self._requests[user_id]) >= self.requests_per_minute:
|
|
return False
|
|
|
|
self._requests[user_id].append(now)
|
|
return True
|
|
|
|
async def __call__(self, user: UserContext = Depends(get_current_user)) -> UserContext:
|
|
"""FastAPI dependency for rate limiting."""
|
|
if not self.is_allowed(user.user_id):
|
|
raise HTTPException(status_code=429, detail="Rate limit exceeded")
|
|
return user
|
|
'''
|
|
|
|
APPLICATION_LOGS = """2024-06-20 10:15:23.456 INFO [main] com.example.app.Application - Starting Application v2.3.1 on server-prod-01 with PID 12345
|
|
2024-06-20 10:15:24.789 INFO [main] com.example.app.config.DatabaseConfig - Initializing connection pool: host=db-primary.internal, port=5432, maxConnections=100
|
|
2024-06-20 10:15:25.123 DEBUG [main] com.example.app.config.DatabaseConfig - Connection pool parameters: minIdle=10, maxWait=30000ms, validationQuery=SELECT 1
|
|
2024-06-20 10:15:26.456 INFO [main] com.example.app.config.CacheConfig - Redis cache configured: host=redis-cluster.internal:6379, cluster=true
|
|
2024-06-20 10:15:27.789 INFO [main] com.example.app.Application - Application started successfully in 4.333 seconds
|
|
2024-06-20 10:15:30.123 INFO [http-nio-8080-exec-1] com.example.app.controller.UserController - GET /api/v2/users?page=1&size=20 - 200 OK (45ms)
|
|
2024-06-20 10:15:31.456 INFO [http-nio-8080-exec-2] com.example.app.controller.UserController - GET /api/v2/users/12345 - 200 OK (12ms)
|
|
2024-06-20 10:15:32.789 WARN [http-nio-8080-exec-3] com.example.app.service.AuthService - Failed login attempt for user: john.doe@example.com from IP: 192.168.1.100
|
|
2024-06-20 10:15:33.012 WARN [http-nio-8080-exec-3] com.example.app.service.AuthService - Account locked after 3 failed attempts: john.doe@example.com
|
|
2024-06-20 10:15:35.456 INFO [http-nio-8080-exec-4] com.example.app.controller.OrderController - POST /api/v2/orders - 201 Created (156ms) - orderId=ORD-2024-00012345
|
|
2024-06-20 10:15:36.789 DEBUG [http-nio-8080-exec-4] com.example.app.service.PaymentService - Processing payment for order ORD-2024-00012345: amount=$149.99, method=CREDIT_CARD
|
|
2024-06-20 10:15:37.123 INFO [http-nio-8080-exec-4] com.example.app.service.PaymentService - Payment successful: transactionId=TXN-abc123def456, status=COMPLETED
|
|
2024-06-20 10:15:40.456 ERROR [http-nio-8080-exec-5] com.example.app.service.InventoryService - Failed to update inventory for SKU-789012: Connection timeout to inventory-service.internal
|
|
2024-06-20 10:15:40.789 ERROR [http-nio-8080-exec-5] com.example.app.service.InventoryService - Stack trace: java.net.SocketTimeoutException: connect timed out
|
|
at java.base/sun.nio.ch.NioSocketImpl.timedFinishConnect(NioSocketImpl.java:546)
|
|
at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:597)
|
|
at java.base/java.net.Socket.connect(Socket.java:633)
|
|
at com.example.app.client.InventoryClient.updateStock(InventoryClient.java:87)
|
|
at com.example.app.service.InventoryService.decrementStock(InventoryService.java:156)
|
|
2024-06-20 10:15:41.123 INFO [scheduler-1] com.example.app.job.CleanupJob - Starting daily cleanup job: removing sessions older than 24 hours
|
|
2024-06-20 10:15:42.456 INFO [scheduler-1] com.example.app.job.CleanupJob - Cleanup completed: removed 1,234 expired sessions, freed 45.6 MB
|
|
2024-06-20 10:15:45.789 INFO [http-nio-8080-exec-6] com.example.app.controller.SearchController - GET /api/v2/search?q=wireless+headphones&category=electronics - 200 OK (234ms) - results=47
|
|
2024-06-20 10:15:50.123 INFO [metrics-reporter] com.example.app.metrics.MetricsReporter - System metrics: cpu=45.2%, memory=67.8% (5.4GB/8GB), activeThreads=23, queuedRequests=0
|
|
"""
|
|
|
|
JAVASCRIPT_FILE_CONTENT = """/**
|
|
* Real-time collaboration module using WebSocket connections.
|
|
* Handles presence, cursors, and document synchronization.
|
|
*
|
|
* @module collaboration
|
|
* @requires socket.io-client
|
|
*/
|
|
|
|
import { io, Socket } from 'socket.io-client';
|
|
import { EventEmitter } from 'events';
|
|
|
|
/** User presence information */
|
|
interface UserPresence {
|
|
userId: string;
|
|
displayName: string;
|
|
color: string;
|
|
cursor?: CursorPosition;
|
|
lastSeen: Date;
|
|
status: 'active' | 'idle' | 'away';
|
|
}
|
|
|
|
/** Cursor position in the document */
|
|
interface CursorPosition {
|
|
line: number;
|
|
column: number;
|
|
selection?: {
|
|
startLine: number;
|
|
startColumn: number;
|
|
endLine: number;
|
|
endColumn: number;
|
|
};
|
|
}
|
|
|
|
/** Document operation for conflict-free replication */
|
|
interface DocumentOperation {
|
|
type: 'insert' | 'delete' | 'replace';
|
|
position: number;
|
|
content?: string;
|
|
length?: number;
|
|
timestamp: number;
|
|
userId: string;
|
|
vectorClock: Record<string, number>;
|
|
}
|
|
|
|
/** Configuration options for collaboration client */
|
|
interface CollaborationConfig {
|
|
serverUrl: string;
|
|
documentId: string;
|
|
userId: string;
|
|
displayName: string;
|
|
reconnectAttempts?: number;
|
|
heartbeatInterval?: number;
|
|
}
|
|
|
|
/**
|
|
* Collaboration client for real-time document editing.
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const collab = new CollaborationClient({
|
|
* serverUrl: 'wss://collab.example.com',
|
|
* documentId: 'doc-123',
|
|
* userId: 'user-456',
|
|
* displayName: 'Alice'
|
|
* });
|
|
*
|
|
* collab.on('presence', (users) => console.log('Active users:', users));
|
|
* collab.on('operation', (op) => applyOperation(op));
|
|
*
|
|
* await collab.connect();
|
|
* ```
|
|
*/
|
|
export class CollaborationClient extends EventEmitter {
|
|
private socket: Socket | null = null;
|
|
private config: CollaborationConfig;
|
|
private presence: Map<string, UserPresence> = new Map();
|
|
private vectorClock: Record<string, number> = {};
|
|
private pendingOperations: DocumentOperation[] = [];
|
|
private reconnectCount = 0;
|
|
private heartbeatTimer: NodeJS.Timer | null = null;
|
|
|
|
constructor(config: CollaborationConfig) {
|
|
super();
|
|
this.config = {
|
|
reconnectAttempts: 5,
|
|
heartbeatInterval: 30000,
|
|
...config
|
|
};
|
|
this.vectorClock[config.userId] = 0;
|
|
}
|
|
|
|
/**
|
|
* Connect to the collaboration server.
|
|
*
|
|
* @returns Promise that resolves when connected
|
|
* @throws Error if connection fails after all retry attempts
|
|
*/
|
|
async connect(): Promise<void> {
|
|
return new Promise((resolve, reject) => {
|
|
this.socket = io(this.config.serverUrl, {
|
|
auth: {
|
|
documentId: this.config.documentId,
|
|
userId: this.config.userId,
|
|
displayName: this.config.displayName
|
|
},
|
|
reconnection: true,
|
|
reconnectionAttempts: this.config.reconnectAttempts
|
|
});
|
|
|
|
this.socket.on('connect', () => {
|
|
console.log(`[Collab] Connected to ${this.config.serverUrl}`);
|
|
this.reconnectCount = 0;
|
|
this.startHeartbeat();
|
|
this.syncPendingOperations();
|
|
resolve();
|
|
});
|
|
|
|
this.socket.on('disconnect', (reason) => {
|
|
console.warn(`[Collab] Disconnected: ${reason}`);
|
|
this.stopHeartbeat();
|
|
this.emit('disconnected', reason);
|
|
});
|
|
|
|
this.socket.on('connect_error', (error) => {
|
|
console.error(`[Collab] Connection error:`, error.message);
|
|
this.reconnectCount++;
|
|
if (this.reconnectCount >= (this.config.reconnectAttempts ?? 5)) {
|
|
reject(new Error(`Failed to connect after ${this.reconnectCount} attempts`));
|
|
}
|
|
});
|
|
|
|
this.setupEventHandlers();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Disconnect from the collaboration server.
|
|
*/
|
|
disconnect(): void {
|
|
this.stopHeartbeat();
|
|
if (this.socket) {
|
|
this.socket.disconnect();
|
|
this.socket = null;
|
|
}
|
|
this.presence.clear();
|
|
console.log('[Collab] Disconnected');
|
|
}
|
|
|
|
/**
|
|
* Send a document operation to other collaborators.
|
|
*
|
|
* @param type - Type of operation
|
|
* @param position - Position in document
|
|
* @param content - Content for insert/replace operations
|
|
* @param length - Length for delete operations
|
|
*/
|
|
sendOperation(
|
|
type: 'insert' | 'delete' | 'replace',
|
|
position: number,
|
|
content?: string,
|
|
length?: number
|
|
): void {
|
|
this.vectorClock[this.config.userId]++;
|
|
|
|
const operation: DocumentOperation = {
|
|
type,
|
|
position,
|
|
content,
|
|
length,
|
|
timestamp: Date.now(),
|
|
userId: this.config.userId,
|
|
vectorClock: { ...this.vectorClock }
|
|
};
|
|
|
|
if (this.socket?.connected) {
|
|
this.socket.emit('operation', operation);
|
|
} else {
|
|
this.pendingOperations.push(operation);
|
|
console.warn('[Collab] Operation queued (offline)');
|
|
}
|
|
|
|
this.emit('localOperation', operation);
|
|
}
|
|
|
|
/**
|
|
* Update cursor position for other collaborators.
|
|
*
|
|
* @param cursor - Current cursor position
|
|
*/
|
|
updateCursor(cursor: CursorPosition): void {
|
|
if (this.socket?.connected) {
|
|
this.socket.emit('cursor', {
|
|
userId: this.config.userId,
|
|
cursor
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all active users in the document.
|
|
*
|
|
* @returns Array of user presence information
|
|
*/
|
|
getActiveUsers(): UserPresence[] {
|
|
return Array.from(this.presence.values()).filter(
|
|
user => user.status !== 'away'
|
|
);
|
|
}
|
|
|
|
private setupEventHandlers(): void {
|
|
if (!this.socket) return;
|
|
|
|
this.socket.on('presence:join', (user: UserPresence) => {
|
|
console.log(`[Collab] ${user.displayName} joined`);
|
|
this.presence.set(user.userId, user);
|
|
this.emit('presence', this.getActiveUsers());
|
|
});
|
|
|
|
this.socket.on('presence:leave', (userId: string) => {
|
|
const user = this.presence.get(userId);
|
|
if (user) {
|
|
console.log(`[Collab] ${user.displayName} left`);
|
|
this.presence.delete(userId);
|
|
this.emit('presence', this.getActiveUsers());
|
|
}
|
|
});
|
|
|
|
this.socket.on('presence:update', (update: Partial<UserPresence> & { userId: string }) => {
|
|
const existing = this.presence.get(update.userId);
|
|
if (existing) {
|
|
this.presence.set(update.userId, { ...existing, ...update });
|
|
this.emit('presence', this.getActiveUsers());
|
|
}
|
|
});
|
|
|
|
this.socket.on('cursor', (data: { userId: string; cursor: CursorPosition }) => {
|
|
const user = this.presence.get(data.userId);
|
|
if (user) {
|
|
user.cursor = data.cursor;
|
|
this.emit('cursor', data);
|
|
}
|
|
});
|
|
|
|
this.socket.on('operation', (operation: DocumentOperation) => {
|
|
// Update vector clock
|
|
for (const [userId, clock] of Object.entries(operation.vectorClock)) {
|
|
this.vectorClock[userId] = Math.max(
|
|
this.vectorClock[userId] || 0,
|
|
clock
|
|
);
|
|
}
|
|
this.emit('operation', operation);
|
|
});
|
|
|
|
this.socket.on('sync', (state: { operations: DocumentOperation[]; presence: UserPresence[] }) => {
|
|
console.log(`[Collab] Syncing ${state.operations.length} operations`);
|
|
state.presence.forEach(user => this.presence.set(user.userId, user));
|
|
state.operations.forEach(op => this.emit('operation', op));
|
|
this.emit('synced');
|
|
});
|
|
}
|
|
|
|
private startHeartbeat(): void {
|
|
this.heartbeatTimer = setInterval(() => {
|
|
if (this.socket?.connected) {
|
|
this.socket.emit('heartbeat', { userId: this.config.userId });
|
|
}
|
|
}, this.config.heartbeatInterval);
|
|
}
|
|
|
|
private stopHeartbeat(): void {
|
|
if (this.heartbeatTimer) {
|
|
clearInterval(this.heartbeatTimer);
|
|
this.heartbeatTimer = null;
|
|
}
|
|
}
|
|
|
|
private syncPendingOperations(): void {
|
|
if (this.pendingOperations.length > 0 && this.socket?.connected) {
|
|
console.log(`[Collab] Syncing ${this.pendingOperations.length} pending operations`);
|
|
this.pendingOperations.forEach(op => this.socket!.emit('operation', op));
|
|
this.pendingOperations = [];
|
|
}
|
|
}
|
|
}
|
|
|
|
export default CollaborationClient;
|
|
"""
|
|
|
|
|
|
# =============================================================================
|
|
# Evaluation Metrics
|
|
# =============================================================================
|
|
|
|
|
|
@dataclass
|
|
class EvalMetrics:
|
|
"""Evaluation metrics for compression quality."""
|
|
|
|
# Basic compression metrics
|
|
original_length: int
|
|
compressed_length: int
|
|
compression_ratio: float
|
|
tokens_before: int
|
|
tokens_after: int
|
|
|
|
# Structure preservation metrics
|
|
keys_preserved: list[str] # JSON keys found in compressed
|
|
keys_missing: list[str] # JSON keys not found in compressed
|
|
key_preservation_ratio: float
|
|
|
|
signatures_preserved: list[str] # Function/class signatures found
|
|
signatures_missing: list[str] # Signatures not found
|
|
signature_preservation_ratio: float
|
|
|
|
# Content type detection
|
|
detected_type: ContentType
|
|
expected_type: ContentType
|
|
detection_correct: bool
|
|
|
|
def __str__(self) -> str:
|
|
lines = [
|
|
f"Compression: {self.compression_ratio:.1%} ({self.original_length} → {self.compressed_length} chars)",
|
|
f"Tokens: {self.tokens_before} → {self.tokens_after} ({self.tokens_before - self.tokens_after} saved)",
|
|
f"Keys: {self.key_preservation_ratio:.0%} ({len(self.keys_preserved)}/{len(self.keys_preserved) + len(self.keys_missing)})",
|
|
f"Signatures: {self.signature_preservation_ratio:.0%} ({len(self.signatures_preserved)}/{len(self.signatures_preserved) + len(self.signatures_missing)})",
|
|
f"Detection: {'✓' if self.detection_correct else '✗'} ({self.detected_type.name})",
|
|
]
|
|
if self.keys_missing:
|
|
lines.append(f"Missing keys: {self.keys_missing[:5]}...")
|
|
if self.signatures_missing:
|
|
lines.append(f"Missing signatures: {self.signatures_missing[:3]}...")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def evaluate_compression(
|
|
result: CompressionResult,
|
|
expected_keys: list[str] | None = None,
|
|
expected_signatures: list[str] | None = None,
|
|
expected_type: ContentType | None = None,
|
|
) -> EvalMetrics:
|
|
"""Evaluate compression quality.
|
|
|
|
Args:
|
|
result: Compression result to evaluate.
|
|
expected_keys: JSON keys that should be preserved.
|
|
expected_signatures: Function/class signatures that should be preserved.
|
|
expected_type: Expected content type.
|
|
|
|
Returns:
|
|
EvalMetrics with detailed evaluation.
|
|
"""
|
|
expected_keys = expected_keys or []
|
|
expected_signatures = expected_signatures or []
|
|
|
|
# Check key preservation
|
|
keys_preserved = [k for k in expected_keys if k in result.compressed]
|
|
keys_missing = [k for k in expected_keys if k not in result.compressed]
|
|
|
|
# Check signature preservation
|
|
signatures_preserved = [s for s in expected_signatures if s in result.compressed]
|
|
signatures_missing = [s for s in expected_signatures if s not in result.compressed]
|
|
|
|
return EvalMetrics(
|
|
original_length=len(result.original),
|
|
compressed_length=len(result.compressed),
|
|
compression_ratio=result.compression_ratio,
|
|
tokens_before=result.tokens_before,
|
|
tokens_after=result.tokens_after,
|
|
keys_preserved=keys_preserved,
|
|
keys_missing=keys_missing,
|
|
key_preservation_ratio=len(keys_preserved) / len(expected_keys) if expected_keys else 1.0,
|
|
signatures_preserved=signatures_preserved,
|
|
signatures_missing=signatures_missing,
|
|
signature_preservation_ratio=len(signatures_preserved) / len(expected_signatures)
|
|
if expected_signatures
|
|
else 1.0,
|
|
detected_type=result.content_type,
|
|
expected_type=expected_type or result.content_type,
|
|
detection_correct=expected_type is None or result.content_type == expected_type,
|
|
)
|
|
|
|
|
|
# =============================================================================
|
|
# Evaluation Tests
|
|
# =============================================================================
|
|
|
|
|
|
class TestJSONAPIResponseEval:
|
|
"""Evaluate compression of JSON API responses."""
|
|
|
|
@pytest.fixture
|
|
def compressor(self):
|
|
"""Create compressor with simple compression (no Kompress for tests)."""
|
|
config = UniversalCompressorConfig(
|
|
use_magika=False, # Use fallback for consistent tests
|
|
use_kompress=False,
|
|
ccr_enabled=False,
|
|
)
|
|
return UniversalCompressor(config=config)
|
|
|
|
def test_github_search_structure_preservation(self, compressor):
|
|
"""Test that GitHub search response keys are preserved."""
|
|
result = compressor.compress(GITHUB_SEARCH_RESPONSE)
|
|
|
|
# Expected keys that must remain visible for LLM to know what to ask for
|
|
expected_keys = [
|
|
"total_count",
|
|
"items",
|
|
"id",
|
|
"name",
|
|
"full_name",
|
|
"owner",
|
|
"login",
|
|
"description",
|
|
"stargazers_count",
|
|
"forks_count",
|
|
"language",
|
|
"topics",
|
|
"default_branch",
|
|
]
|
|
|
|
metrics = evaluate_compression(
|
|
result,
|
|
expected_keys=expected_keys,
|
|
expected_type=ContentType.JSON,
|
|
)
|
|
|
|
print(f"\n{metrics}")
|
|
|
|
# Critical assertion: all keys must be preserved
|
|
assert metrics.key_preservation_ratio == 1.0, f"Missing keys: {metrics.keys_missing}"
|
|
|
|
# Should achieve some compression on the long descriptions
|
|
assert metrics.compression_ratio < 1.0, "Should compress the response"
|
|
|
|
def test_nested_user_data_preservation(self, compressor):
|
|
"""Test preservation of nested user data structure."""
|
|
user_data = json.dumps(
|
|
{
|
|
"user": {
|
|
"id": "usr_8f14e45f-ceea-4123-8f14-e45fceea4123",
|
|
"profile": {
|
|
"displayName": "Alice Smith",
|
|
"email": "alice@example.com",
|
|
"bio": "Software engineer passionate about distributed systems and machine learning. "
|
|
* 10,
|
|
"avatar": "https://avatars.example.com/u/12345?v=4",
|
|
},
|
|
"preferences": {
|
|
"theme": "dark",
|
|
"language": "en-US",
|
|
"notifications": {
|
|
"email": True,
|
|
"push": False,
|
|
"sms": False,
|
|
},
|
|
},
|
|
"stats": {
|
|
"repositories": 42,
|
|
"contributions": 1250,
|
|
"followers": 89,
|
|
"following": 23,
|
|
},
|
|
},
|
|
"metadata": {
|
|
"requestId": "req_abc123def456",
|
|
"timestamp": "2024-06-20T15:30:00Z",
|
|
"version": "v2",
|
|
},
|
|
},
|
|
indent=2,
|
|
)
|
|
|
|
result = compressor.compress(user_data)
|
|
|
|
expected_keys = [
|
|
"user",
|
|
"id",
|
|
"profile",
|
|
"displayName",
|
|
"email",
|
|
"bio",
|
|
"avatar",
|
|
"preferences",
|
|
"theme",
|
|
"language",
|
|
"notifications",
|
|
"stats",
|
|
"repositories",
|
|
"contributions",
|
|
"followers",
|
|
"metadata",
|
|
"requestId",
|
|
"timestamp",
|
|
"version",
|
|
]
|
|
|
|
metrics = evaluate_compression(
|
|
result,
|
|
expected_keys=expected_keys,
|
|
expected_type=ContentType.JSON,
|
|
)
|
|
|
|
print(f"\n{metrics}")
|
|
|
|
assert metrics.key_preservation_ratio == 1.0, f"Missing keys: {metrics.keys_missing}"
|
|
|
|
# UUID should be preserved (high entropy)
|
|
assert (
|
|
"8f14e45f-ceea-4123-8f14-e45fceea4123" in result.compressed
|
|
or "usr_8f14e45f" in result.compressed
|
|
), "UUID should be preserved due to high entropy"
|
|
|
|
|
|
class TestCodeFileEval:
|
|
"""Evaluate compression of code files."""
|
|
|
|
@pytest.fixture
|
|
def compressor(self):
|
|
"""Create compressor."""
|
|
config = UniversalCompressorConfig(
|
|
use_magika=False,
|
|
use_kompress=False,
|
|
ccr_enabled=False,
|
|
)
|
|
return UniversalCompressor(config=config)
|
|
|
|
def test_python_signatures_preserved(self, compressor):
|
|
"""Test that Python function/class signatures are preserved."""
|
|
result = compressor.compress(PYTHON_FILE_CONTENT)
|
|
|
|
expected_signatures = [
|
|
"class TokenPayload",
|
|
"class UserContext",
|
|
"class RateLimiter",
|
|
"def create_access_token",
|
|
"def decode_token",
|
|
"async def get_current_user",
|
|
"def require_roles",
|
|
"def require_permissions",
|
|
"def is_allowed",
|
|
]
|
|
|
|
metrics = evaluate_compression(
|
|
result,
|
|
expected_signatures=expected_signatures,
|
|
expected_type=ContentType.CODE,
|
|
)
|
|
|
|
print(f"\n{metrics}")
|
|
|
|
# At least 80% of signatures should be preserved
|
|
assert metrics.signature_preservation_ratio >= 0.8, (
|
|
f"Missing signatures: {metrics.signatures_missing}"
|
|
)
|
|
|
|
def test_javascript_signatures_preserved(self, compressor):
|
|
"""Test that JavaScript/TypeScript signatures are preserved."""
|
|
result = compressor.compress(JAVASCRIPT_FILE_CONTENT)
|
|
|
|
expected_signatures = [
|
|
"interface UserPresence",
|
|
"interface CursorPosition",
|
|
"interface DocumentOperation",
|
|
"interface CollaborationConfig",
|
|
"class CollaborationClient",
|
|
"async connect()",
|
|
"disconnect()",
|
|
"sendOperation(",
|
|
"updateCursor(",
|
|
"getActiveUsers()",
|
|
]
|
|
|
|
metrics = evaluate_compression(
|
|
result,
|
|
expected_signatures=expected_signatures,
|
|
expected_type=ContentType.CODE,
|
|
)
|
|
|
|
print(f"\n{metrics}")
|
|
|
|
# At least 60% of signatures should be preserved
|
|
# (methods inside class bodies may be compressed, which is expected)
|
|
assert metrics.signature_preservation_ratio >= 0.6, (
|
|
f"Missing signatures: {metrics.signatures_missing}"
|
|
)
|
|
|
|
|
|
class TestLogOutputEval:
|
|
"""Evaluate compression of log outputs.
|
|
|
|
NOTE: Without a specialized LogHandler, logs are handled by NoOpHandler
|
|
which marks everything compressible. This results in aggressive compression.
|
|
A future LogHandler should preserve:
|
|
- Log levels (INFO, WARN, ERROR)
|
|
- Timestamps
|
|
- Component names
|
|
- Key identifiers (order IDs, transaction IDs)
|
|
- Error types and stack traces
|
|
"""
|
|
|
|
@pytest.fixture
|
|
def compressor(self):
|
|
"""Create compressor."""
|
|
config = UniversalCompressorConfig(
|
|
use_magika=False,
|
|
use_kompress=False,
|
|
ccr_enabled=False,
|
|
)
|
|
return UniversalCompressor(config=config)
|
|
|
|
def test_log_compression_report(self, compressor):
|
|
"""Report log compression behavior (no specialized handler yet)."""
|
|
result = compressor.compress(APPLICATION_LOGS)
|
|
|
|
# Key information we'd like to preserve
|
|
desired_content = [
|
|
"INFO",
|
|
"WARN",
|
|
"ERROR",
|
|
"DEBUG", # Log levels
|
|
"Application",
|
|
"DatabaseConfig",
|
|
"CacheConfig", # Components
|
|
"Starting",
|
|
"Initializing",
|
|
"Failed", # Key actions
|
|
"ORD-2024-00012345", # Order ID
|
|
"TXN-abc123def456", # Transaction ID
|
|
"SocketTimeoutException", # Error type
|
|
]
|
|
|
|
preserved = [c for c in desired_content if c in result.compressed]
|
|
missing = [c for c in desired_content if c not in result.compressed]
|
|
|
|
print(f"\nLog compression: {result.compression_ratio:.1%}")
|
|
print(f"Preserved: {len(preserved)}/{len(desired_content)}")
|
|
print(f" Found: {preserved}")
|
|
if missing:
|
|
print(f" Missing (needs LogHandler): {missing}")
|
|
|
|
# Basic assertion: some compression happened
|
|
assert result.compression_ratio < 1.0, "Should compress logs"
|
|
|
|
# Note: with a LogHandler, we'd assert 70%+ preservation
|
|
# For now, just verify content type detection
|
|
assert result.content_type in [ContentType.TEXT, ContentType.LOG], (
|
|
f"Should detect as TEXT or LOG, got {result.content_type}"
|
|
)
|
|
|
|
|
|
class TestMultiToolAgentScenario:
|
|
"""Test realistic multi-tool agent scenario.
|
|
|
|
Simulates an agent that:
|
|
1. Searches GitHub for repositories
|
|
2. Reads a code file
|
|
3. Checks application logs
|
|
|
|
All tool outputs should be compressible while preserving
|
|
key structure that lets the LLM know what's available.
|
|
"""
|
|
|
|
@pytest.fixture
|
|
def compressor(self):
|
|
"""Create compressor."""
|
|
config = UniversalCompressorConfig(
|
|
use_magika=False,
|
|
use_kompress=False,
|
|
ccr_enabled=False,
|
|
)
|
|
return UniversalCompressor(config=config)
|
|
|
|
def test_batch_compress_mixed_content(self, compressor):
|
|
"""Test batch compression of mixed tool outputs."""
|
|
tool_outputs = [
|
|
GITHUB_SEARCH_RESPONSE,
|
|
PYTHON_FILE_CONTENT,
|
|
APPLICATION_LOGS,
|
|
]
|
|
|
|
results = compressor.compress_batch(tool_outputs)
|
|
|
|
assert len(results) == 3
|
|
|
|
# Check content type detection
|
|
assert results[0].content_type == ContentType.JSON
|
|
assert results[1].content_type == ContentType.CODE
|
|
# Logs may be detected as TEXT or LOG depending on detector
|
|
assert results[2].content_type in [ContentType.TEXT, ContentType.LOG, ContentType.CODE]
|
|
|
|
# All should achieve some compression
|
|
total_original = sum(r.tokens_before for r in results)
|
|
total_compressed = sum(r.tokens_after for r in results)
|
|
|
|
print("\nBatch compression results:")
|
|
print(f" Total tokens: {total_original} → {total_compressed}")
|
|
print(
|
|
f" Savings: {total_original - total_compressed} tokens ({(1 - total_compressed / total_original):.1%})"
|
|
)
|
|
|
|
for result in results:
|
|
print(f" [{result.content_type.name}] {result.compression_ratio:.1%} compression")
|
|
|
|
def test_agent_context_window_simulation(self, compressor):
|
|
"""Simulate agent compressing tool outputs to fit context window.
|
|
|
|
Scenario: Agent has 8K token limit, tools returned ~4K tokens.
|
|
After compression, should fit comfortably with room for reasoning.
|
|
"""
|
|
# Combine all tool outputs (simulating agent context)
|
|
combined_context = f"""
|
|
## Tool Output 1: GitHub Search Results
|
|
```json
|
|
{GITHUB_SEARCH_RESPONSE}
|
|
```
|
|
|
|
## Tool Output 2: File Content (auth.py)
|
|
```python
|
|
{PYTHON_FILE_CONTENT}
|
|
```
|
|
|
|
## Tool Output 3: Application Logs
|
|
```
|
|
{APPLICATION_LOGS}
|
|
```
|
|
"""
|
|
|
|
# Estimate original tokens (~4 chars per token)
|
|
original_tokens = len(combined_context) // 4
|
|
print(f"\nOriginal context: ~{original_tokens} tokens")
|
|
|
|
# Compress each section
|
|
results = compressor.compress_batch(
|
|
[
|
|
GITHUB_SEARCH_RESPONSE,
|
|
PYTHON_FILE_CONTENT,
|
|
APPLICATION_LOGS,
|
|
]
|
|
)
|
|
|
|
total_compressed_tokens = sum(r.tokens_after for r in results)
|
|
|
|
print(f"After compression: ~{total_compressed_tokens} tokens")
|
|
print(f"Savings: ~{original_tokens - total_compressed_tokens} tokens")
|
|
|
|
# Verify key information is still accessible
|
|
# JSON: Can see repository names and keys
|
|
assert "headroom" in results[0].compressed
|
|
assert "stargazers_count" in results[0].compressed
|
|
|
|
# Code: Can see function signatures
|
|
assert "create_access_token" in results[1].compressed
|
|
|
|
# Logs: Can see error indicators
|
|
# (may vary based on compression)
|
|
|
|
|
|
class TestCompressionQualityMetrics:
|
|
"""Test overall compression quality metrics."""
|
|
|
|
@pytest.fixture
|
|
def compressor(self):
|
|
"""Create compressor."""
|
|
config = UniversalCompressorConfig(
|
|
use_magika=False,
|
|
use_kompress=False,
|
|
ccr_enabled=False,
|
|
compression_ratio_target=0.3, # Target 70% reduction
|
|
)
|
|
return UniversalCompressor(config=config)
|
|
|
|
def test_compression_preserves_retrievability(self, compressor):
|
|
"""Test that compressed content still allows CCR retrieval.
|
|
|
|
Key insight: The compressed content should contain enough
|
|
structure that an LLM can identify what data exists and
|
|
request specific items via CCR.
|
|
"""
|
|
# Large JSON with many items
|
|
data = {
|
|
"results": [
|
|
{
|
|
"id": f"item_{i}",
|
|
"title": f"Product {i}",
|
|
"description": f"This is a detailed description for product {i}. " * 5,
|
|
"price": 19.99 + i,
|
|
"category": ["electronics", "gadgets", "accessories"][i % 3],
|
|
"tags": [f"tag_{j}" for j in range(5)],
|
|
}
|
|
for i in range(20)
|
|
],
|
|
"pagination": {
|
|
"page": 1,
|
|
"total_pages": 10,
|
|
"total_items": 200,
|
|
},
|
|
}
|
|
content = json.dumps(data, indent=2)
|
|
|
|
result = compressor.compress(content)
|
|
|
|
# Check that schema is discoverable
|
|
# LLM should be able to see: results array with items having id, title, etc.
|
|
discoverable_keys = ["results", "id", "title", "price", "category", "pagination"]
|
|
|
|
discovered = [k for k in discoverable_keys if k in result.compressed]
|
|
|
|
print("\nRetrievability test:")
|
|
print(f" Original: {len(content)} chars, ~{len(content) // 4} tokens")
|
|
print(
|
|
f" Compressed: {len(result.compressed)} chars, ~{len(result.compressed) // 4} tokens"
|
|
)
|
|
print(f" Discoverable keys: {len(discovered)}/{len(discoverable_keys)}")
|
|
|
|
# All structural keys must be discoverable
|
|
assert len(discovered) == len(discoverable_keys), (
|
|
f"Missing keys for CCR discovery: {set(discoverable_keys) - set(discovered)}"
|
|
)
|
|
|
|
def test_summary_report(self, compressor):
|
|
"""Generate summary report of compression quality across all test fixtures."""
|
|
test_cases = [
|
|
("GitHub API Response", GITHUB_SEARCH_RESPONSE, ContentType.JSON),
|
|
("Python Auth Module", PYTHON_FILE_CONTENT, ContentType.CODE),
|
|
("JavaScript Collab Module", JAVASCRIPT_FILE_CONTENT, ContentType.CODE),
|
|
("Application Logs", APPLICATION_LOGS, ContentType.TEXT),
|
|
]
|
|
|
|
print("\n" + "=" * 60)
|
|
print("COMPRESSION QUALITY SUMMARY REPORT")
|
|
print("=" * 60)
|
|
|
|
total_original = 0
|
|
total_compressed = 0
|
|
|
|
for name, content, expected_type in test_cases:
|
|
result = compressor.compress(content)
|
|
total_original += result.tokens_before
|
|
total_compressed += result.tokens_after
|
|
|
|
print(f"\n{name}:")
|
|
print(f" Type: {result.content_type.name} (expected: {expected_type.name})")
|
|
print(f" Compression: {result.compression_ratio:.1%}")
|
|
print(f" Tokens: {result.tokens_before} → {result.tokens_after}")
|
|
print(f" Handler: {result.handler_used}")
|
|
|
|
print("\n" + "-" * 60)
|
|
print(f"TOTAL: {total_original} → {total_compressed} tokens")
|
|
print(
|
|
f"OVERALL SAVINGS: {total_original - total_compressed} tokens ({(1 - total_compressed / total_original):.1%})"
|
|
)
|
|
print("=" * 60)
|