mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-23 20:23:00 +00:00
feat: Replace hanzo-agents with hanzo-agent submodule
Consolidates agent functionality into unified hanzo-agent SDK: Changes: - Removed old pkg/hanzo-agents package (deprecated) - Added hanzo-agent as git submodule at pkg/hanzo-agent - Updated pyproject.toml to remove hanzo-agents test exclusion - Configured .gitmodules for github.com/hanzoai/agent Benefits: - Single source of truth for agent functionality - Modular extensions (web3, tee, marketplace, cli) - Maintains both standalone and integrated use cases - Users can install: pip install "hanzoai[full]" Migration: - Old: from hanzo_agents import Agent - New: from agents import Agent (in hanzo-agent submodule) Related: Agent SDK now at github.com/hanzoai/agent
This commit is contained in:
@@ -0,0 +1,320 @@
|
||||
"""Test enhanced Training-Free GRPO features with full Tencent parity."""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_enhanced_imports():
|
||||
"""Test that all enhanced GRPO components can be imported."""
|
||||
from hanzoai.grpo import (
|
||||
EnhancedAPIModelAdapter,
|
||||
EnhancedAPIModelConfig,
|
||||
EnhancedDeepSeekAdapter,
|
||||
EnhancedLLMClient,
|
||||
EnhancedOpenAIAdapter,
|
||||
EnhancedSemanticExtractor,
|
||||
EnhancedTrajectory,
|
||||
rollout_with_timeout,
|
||||
)
|
||||
|
||||
assert EnhancedSemanticExtractor is not None
|
||||
assert EnhancedLLMClient is not None
|
||||
assert EnhancedTrajectory is not None
|
||||
assert EnhancedAPIModelAdapter is not None
|
||||
assert EnhancedDeepSeekAdapter is not None
|
||||
assert EnhancedOpenAIAdapter is not None
|
||||
assert EnhancedAPIModelConfig is not None
|
||||
assert rollout_with_timeout is not None
|
||||
print("✓ All enhanced imports successful")
|
||||
|
||||
|
||||
def test_enhanced_trajectory():
|
||||
"""Test enhanced trajectory with reasoning and retry count."""
|
||||
from hanzoai.grpo import EnhancedTrajectory
|
||||
|
||||
traj = EnhancedTrajectory(
|
||||
query="What is 2+2?",
|
||||
output="The answer is 4.",
|
||||
reward=1.0,
|
||||
groundtruth="4",
|
||||
reasoning="First, I identify this as addition. Then I add 2+2=4.",
|
||||
retry_count=1,
|
||||
task_time=1.5,
|
||||
)
|
||||
|
||||
assert traj.query == "What is 2+2?"
|
||||
assert traj.reasoning == "First, I identify this as addition. Then I add 2+2=4."
|
||||
assert traj.retry_count == 1
|
||||
assert traj.task_time == 1.5
|
||||
print("✓ Enhanced trajectory works")
|
||||
|
||||
|
||||
def test_enhanced_config_from_env():
|
||||
"""Test enhanced config creation from environment variables."""
|
||||
from hanzoai.grpo import EnhancedAPIModelConfig
|
||||
|
||||
# Set test env vars
|
||||
os.environ["HANZO_GRPO_API_KEY"] = "test-key"
|
||||
os.environ["HANZO_GRPO_BASE_URL"] = "https://test.api.com"
|
||||
os.environ["HANZO_GRPO_MODEL"] = "test-model"
|
||||
os.environ["HANZO_GRPO_MAX_RETRIES"] = "5"
|
||||
|
||||
config = EnhancedAPIModelConfig.from_env()
|
||||
|
||||
assert config.api_key == "test-key"
|
||||
assert config.base_url == "https://test.api.com"
|
||||
assert config.model == "test-model"
|
||||
assert config.max_retries == 5
|
||||
|
||||
# Test override
|
||||
config2 = EnhancedAPIModelConfig.from_env(model="override-model")
|
||||
assert config2.model == "override-model"
|
||||
|
||||
# Cleanup
|
||||
del os.environ["HANZO_GRPO_API_KEY"]
|
||||
del os.environ["HANZO_GRPO_BASE_URL"]
|
||||
del os.environ["HANZO_GRPO_MODEL"]
|
||||
del os.environ["HANZO_GRPO_MAX_RETRIES"]
|
||||
|
||||
print("✓ Enhanced config from env works")
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not os.getenv("DEEPSEEK_API_KEY"),
|
||||
reason="DEEPSEEK_API_KEY not set",
|
||||
)
|
||||
def test_enhanced_llm_client_with_retry():
|
||||
"""Test enhanced LLM client with retry logic."""
|
||||
from hanzoai.grpo import EnhancedLLMClient
|
||||
|
||||
api_key = os.getenv("DEEPSEEK_API_KEY")
|
||||
client = EnhancedLLMClient(
|
||||
api_key=api_key,
|
||||
base_url="https://api.deepseek.com/v1",
|
||||
model="deepseek-chat",
|
||||
max_retries=3,
|
||||
)
|
||||
|
||||
# Test basic chat
|
||||
response = client.chat("What is 2+2?", max_tokens=50)
|
||||
assert isinstance(response, str)
|
||||
assert len(response) > 0
|
||||
print(f"✓ Enhanced LLM client works (response: {len(response)} chars)")
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not os.getenv("DEEPSEEK_API_KEY"),
|
||||
reason="DEEPSEEK_API_KEY not set",
|
||||
)
|
||||
def test_enhanced_deepseek_adapter():
|
||||
"""Test enhanced DeepSeek adapter with retry and env config."""
|
||||
from hanzoai.grpo import EnhancedDeepSeekAdapter
|
||||
|
||||
api_key = os.getenv("DEEPSEEK_API_KEY")
|
||||
adapter = EnhancedDeepSeekAdapter(api_key=api_key, max_retries=2)
|
||||
|
||||
# Test basic generation
|
||||
response = adapter.generate("What is 3+3?", max_tokens=50)
|
||||
assert isinstance(response, str)
|
||||
assert len(response) > 0
|
||||
|
||||
print(f"✓ Enhanced DeepSeek adapter works (response: {len(response)} chars)")
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not os.getenv("DEEPSEEK_API_KEY"),
|
||||
reason="DEEPSEEK_API_KEY not set",
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_enhanced_adapter_async():
|
||||
"""Test enhanced adapter async generation."""
|
||||
from hanzoai.grpo import EnhancedDeepSeekAdapter
|
||||
|
||||
api_key = os.getenv("DEEPSEEK_API_KEY")
|
||||
adapter = EnhancedDeepSeekAdapter(api_key=api_key)
|
||||
|
||||
# Test async generation
|
||||
response = await adapter.generate_async("What is 4+4?", max_tokens=50, timeout=30.0)
|
||||
assert isinstance(response, str)
|
||||
assert len(response) > 0
|
||||
print(f"✓ Enhanced async generation works (response: {len(response)} chars)")
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not os.getenv("DEEPSEEK_API_KEY"),
|
||||
reason="DEEPSEEK_API_KEY not set",
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_rollout_with_timeout():
|
||||
"""Test async rollout with timeout."""
|
||||
from hanzoai.grpo import EnhancedDeepSeekAdapter, rollout_with_timeout
|
||||
|
||||
api_key = os.getenv("DEEPSEEK_API_KEY")
|
||||
adapter = EnhancedDeepSeekAdapter(api_key=api_key)
|
||||
|
||||
# Test rollout with timeout
|
||||
result = await rollout_with_timeout(
|
||||
generate_func=adapter.generate,
|
||||
query="What is 5+5?",
|
||||
timeout=30.0,
|
||||
max_retries=2,
|
||||
max_tokens=50,
|
||||
)
|
||||
|
||||
assert isinstance(result, str)
|
||||
assert len(result) > 0
|
||||
print(f"✓ Rollout with timeout works (response: {len(result)} chars)")
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not os.getenv("DEEPSEEK_API_KEY"),
|
||||
reason="DEEPSEEK_API_KEY not set",
|
||||
)
|
||||
def test_enhanced_semantic_extractor_with_caching():
|
||||
"""Test enhanced semantic extractor with file caching."""
|
||||
from hanzoai.grpo import (
|
||||
EnhancedDeepSeekAdapter,
|
||||
EnhancedLLMClient,
|
||||
EnhancedSemanticExtractor,
|
||||
EnhancedTrajectory,
|
||||
ExperienceManager,
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
api_key = os.getenv("DEEPSEEK_API_KEY")
|
||||
|
||||
# Initialize components
|
||||
llm_client = EnhancedLLMClient(
|
||||
api_key=api_key,
|
||||
max_retries=2,
|
||||
)
|
||||
|
||||
extractor = EnhancedSemanticExtractor(
|
||||
llm_client=llm_client,
|
||||
max_operations=2,
|
||||
max_workers=2,
|
||||
cache_dir=tmpdir,
|
||||
enable_caching=True,
|
||||
enable_parallel=True,
|
||||
filter_partial_correct=False, # Disable for test
|
||||
)
|
||||
|
||||
# Create test trajectories
|
||||
trajectories = [
|
||||
EnhancedTrajectory(
|
||||
query="What is 2x + 5 = 13?",
|
||||
output="Let me solve: 2x = 13 - 5 = 8, so x = 4",
|
||||
reward=1.0,
|
||||
groundtruth="4",
|
||||
),
|
||||
EnhancedTrajectory(
|
||||
query="What is 2x + 5 = 13?",
|
||||
output="2x = 8, x = 3", # Wrong
|
||||
reward=0.0,
|
||||
groundtruth="4",
|
||||
),
|
||||
]
|
||||
|
||||
# Test Stage 1: Summarize with caching
|
||||
summarized = extractor.summarize_trajectories(trajectories, use_groundtruth=True)
|
||||
|
||||
assert len(summarized) > 0
|
||||
assert "What is 2x + 5 = 13?" in summarized
|
||||
|
||||
# Check cache file created
|
||||
cache_file = Path(tmpdir) / "single_rollout_summary.json"
|
||||
assert cache_file.exists()
|
||||
|
||||
# Test loading from cache (should be instant)
|
||||
summarized2 = extractor.summarize_trajectories(trajectories, use_groundtruth=True)
|
||||
assert summarized == summarized2
|
||||
|
||||
print(f"✓ Enhanced semantic extractor with caching works")
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not os.getenv("DEEPSEEK_API_KEY"),
|
||||
reason="DEEPSEEK_API_KEY not set",
|
||||
)
|
||||
def test_enhanced_partial_correct_filtering():
|
||||
"""Test partial correct filtering feature."""
|
||||
from hanzoai.grpo import (
|
||||
EnhancedLLMClient,
|
||||
EnhancedSemanticExtractor,
|
||||
EnhancedTrajectory,
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
api_key = os.getenv("DEEPSEEK_API_KEY")
|
||||
|
||||
llm_client = EnhancedLLMClient(api_key=api_key)
|
||||
|
||||
extractor = EnhancedSemanticExtractor(
|
||||
llm_client=llm_client,
|
||||
cache_dir=tmpdir,
|
||||
filter_partial_correct=True, # Enable filtering
|
||||
)
|
||||
|
||||
# Create all-correct group (should be filtered out)
|
||||
all_correct = [
|
||||
EnhancedTrajectory(query="Q1", output="A1", reward=1.0),
|
||||
EnhancedTrajectory(query="Q1", output="A1", reward=1.0),
|
||||
]
|
||||
|
||||
# Create partial-correct group (should be processed)
|
||||
partial_correct = [
|
||||
EnhancedTrajectory(query="Q2", output="A2a", reward=1.0, groundtruth="X"),
|
||||
EnhancedTrajectory(query="Q2", output="A2b", reward=0.0, groundtruth="X"),
|
||||
]
|
||||
|
||||
trajectories = all_correct + partial_correct
|
||||
|
||||
# Summarize with filtering
|
||||
summarized = extractor.summarize_trajectories(trajectories, use_groundtruth=True)
|
||||
|
||||
# Should only have Q2 (partial correct)
|
||||
assert "Q2" in summarized
|
||||
# Q1 should be filtered out
|
||||
assert "Q1" not in summarized
|
||||
|
||||
print("✓ Partial correct filtering works")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("Enhanced Training-Free GRPO Tests")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
test_enhanced_imports()
|
||||
test_enhanced_trajectory()
|
||||
test_enhanced_config_from_env()
|
||||
|
||||
if os.getenv("DEEPSEEK_API_KEY"):
|
||||
print("\n--- DeepSeek API Tests ---")
|
||||
test_enhanced_llm_client_with_retry()
|
||||
test_enhanced_deepseek_adapter()
|
||||
|
||||
print("\n--- Async Tests ---")
|
||||
asyncio.run(test_enhanced_adapter_async())
|
||||
asyncio.run(test_rollout_with_timeout())
|
||||
|
||||
print("\n--- Advanced Features Tests ---")
|
||||
test_enhanced_semantic_extractor_with_caching()
|
||||
test_enhanced_partial_correct_filtering()
|
||||
else:
|
||||
print("\n⚠ Skipping API tests (DEEPSEEK_API_KEY not set)")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("✅ ALL ENHANCED TESTS PASSED!")
|
||||
print("=" * 60)
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ TEST FAILED: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
@@ -0,0 +1,236 @@
|
||||
"""Test Training-Free GRPO integration in Python SDK."""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_grpo_imports():
|
||||
"""Test that all GRPO components can be imported."""
|
||||
from hanzoai.grpo import (
|
||||
APIModelAdapter,
|
||||
APIModelConfig,
|
||||
DeepSeekAdapter,
|
||||
ExperienceManager,
|
||||
LLMClient,
|
||||
OpenAIAdapter,
|
||||
SemanticExtractor,
|
||||
Trajectory,
|
||||
)
|
||||
|
||||
# Verify classes exist
|
||||
assert ExperienceManager is not None
|
||||
assert SemanticExtractor is not None
|
||||
assert Trajectory is not None
|
||||
assert LLMClient is not None
|
||||
assert APIModelAdapter is not None
|
||||
assert DeepSeekAdapter is not None
|
||||
assert OpenAIAdapter is not None
|
||||
assert APIModelConfig is not None
|
||||
|
||||
|
||||
def test_experience_manager_basic():
|
||||
"""Test basic ExperienceManager functionality."""
|
||||
from hanzoai.grpo import ExperienceManager
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
exp_path = Path(tmpdir) / "experiences.json"
|
||||
|
||||
# Create manager
|
||||
manager = ExperienceManager(checkpoint_path=str(exp_path))
|
||||
|
||||
# Add experiences
|
||||
exp_id1 = manager.add("When solving equations, isolate the variable.")
|
||||
exp_id2 = manager.add("Always check your answer by substitution.")
|
||||
|
||||
assert len(manager.experiences) == 2
|
||||
assert exp_id1 == "G0"
|
||||
assert exp_id2 == "G1"
|
||||
|
||||
# Format for prompt
|
||||
formatted = manager.format_for_prompt()
|
||||
assert "[G0]" in formatted
|
||||
assert "[G1]" in formatted
|
||||
assert "isolate the variable" in formatted
|
||||
|
||||
# Save and reload
|
||||
manager.save(str(exp_path))
|
||||
assert exp_path.exists()
|
||||
|
||||
# Load in new manager
|
||||
manager2 = ExperienceManager(checkpoint_path=str(exp_path))
|
||||
assert len(manager2.experiences) == 2
|
||||
assert manager2.experiences[exp_id1] == manager.experiences[exp_id1]
|
||||
|
||||
|
||||
def test_trajectory_dataclass():
|
||||
"""Test Trajectory dataclass."""
|
||||
from hanzoai.grpo import Trajectory
|
||||
|
||||
traj = Trajectory(
|
||||
query="What is 2+2?",
|
||||
output="The answer is 4.",
|
||||
reward=1.0,
|
||||
groundtruth="4",
|
||||
)
|
||||
|
||||
assert traj.query == "What is 2+2?"
|
||||
assert traj.output == "The answer is 4."
|
||||
assert traj.reward == 1.0
|
||||
assert traj.groundtruth == "4"
|
||||
assert traj.summary is None
|
||||
|
||||
|
||||
def test_api_model_config():
|
||||
"""Test APIModelConfig dataclass."""
|
||||
from hanzoai.grpo import APIModelConfig
|
||||
|
||||
config = APIModelConfig(
|
||||
api_key="test-key",
|
||||
base_url="https://api.deepseek.com/v1",
|
||||
model="deepseek-chat",
|
||||
temperature=0.7,
|
||||
)
|
||||
|
||||
assert config.api_key == "test-key"
|
||||
assert config.base_url == "https://api.deepseek.com/v1"
|
||||
assert config.model == "deepseek-chat"
|
||||
assert config.temperature == 0.7
|
||||
|
||||
|
||||
def test_experience_operations():
|
||||
"""Test experience CRUD operations."""
|
||||
from hanzoai.grpo import ExperienceManager
|
||||
|
||||
manager = ExperienceManager()
|
||||
|
||||
# Add
|
||||
id1 = manager.add("Experience 1")
|
||||
id2 = manager.add("Experience 2")
|
||||
id3 = manager.add("Experience 3")
|
||||
|
||||
assert len(manager.experiences) == 3
|
||||
|
||||
# Delete
|
||||
manager.delete(id2)
|
||||
assert len(manager.experiences) == 2
|
||||
assert id2 not in manager.experiences
|
||||
|
||||
# Modify
|
||||
manager.modify(id1, "Modified Experience 1")
|
||||
assert manager.experiences[id1] == "Modified Experience 1"
|
||||
|
||||
# Merge
|
||||
merged_id = manager.merge([id1, id3], "Merged Experience")
|
||||
assert merged_id in manager.experiences
|
||||
assert manager.experiences[merged_id] == "Merged Experience"
|
||||
assert id1 not in manager.experiences
|
||||
assert id3 not in manager.experiences
|
||||
|
||||
|
||||
def test_apply_operations():
|
||||
"""Test applying batch operations."""
|
||||
from hanzoai.grpo import ExperienceManager
|
||||
|
||||
manager = ExperienceManager()
|
||||
id1 = manager.add("Experience 1")
|
||||
id2 = manager.add("Experience 2")
|
||||
|
||||
operations = [
|
||||
{"option": "add", "experience": "New experience from LLM"},
|
||||
{"option": "modify", "modified_from": id1, "experience": "Modified by LLM"},
|
||||
{"option": "delete", "delete_id": id2},
|
||||
]
|
||||
|
||||
manager.apply_operations(operations)
|
||||
|
||||
assert len(manager.experiences) == 2 # 1 original - 1 deleted + 1 added
|
||||
assert manager.experiences[id1] == "Modified by LLM"
|
||||
assert id2 not in manager.experiences
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not os.getenv("DEEPSEEK_API_KEY"),
|
||||
reason="DEEPSEEK_API_KEY not set",
|
||||
)
|
||||
def test_deepseek_adapter_basic():
|
||||
"""Test DeepSeekAdapter basic functionality (requires API key)."""
|
||||
from hanzoai.grpo import DeepSeekAdapter
|
||||
|
||||
api_key = os.getenv("DEEPSEEK_API_KEY")
|
||||
adapter = DeepSeekAdapter(api_key=api_key, model="deepseek-chat")
|
||||
|
||||
# Test basic generation
|
||||
response = adapter.generate("What is 2+2?", max_tokens=50)
|
||||
assert isinstance(response, str)
|
||||
assert len(response) > 0
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not os.getenv("DEEPSEEK_API_KEY"),
|
||||
reason="DEEPSEEK_API_KEY not set",
|
||||
)
|
||||
def test_generate_with_experiences():
|
||||
"""Test generation with experience injection."""
|
||||
from hanzoai.grpo import DeepSeekAdapter, ExperienceManager
|
||||
|
||||
api_key = os.getenv("DEEPSEEK_API_KEY")
|
||||
adapter = DeepSeekAdapter(api_key=api_key)
|
||||
exp_manager = ExperienceManager()
|
||||
|
||||
# Add some experiences
|
||||
exp_manager.add("When solving math problems, show your work step by step.")
|
||||
exp_manager.add("Always check your answer makes sense.")
|
||||
|
||||
# Generate with experiences
|
||||
query = "What is 15 * 7?"
|
||||
response = adapter.generate_with_experiences(
|
||||
query=query,
|
||||
experiences=exp_manager.format_for_prompt(),
|
||||
max_tokens=200,
|
||||
)
|
||||
|
||||
assert isinstance(response, str)
|
||||
assert len(response) > 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run basic tests without pytest
|
||||
print("Testing GRPO imports...")
|
||||
test_grpo_imports()
|
||||
print("✓ Imports successful")
|
||||
|
||||
print("\nTesting ExperienceManager...")
|
||||
test_experience_manager_basic()
|
||||
print("✓ ExperienceManager works")
|
||||
|
||||
print("\nTesting Trajectory dataclass...")
|
||||
test_trajectory_dataclass()
|
||||
print("✓ Trajectory works")
|
||||
|
||||
print("\nTesting APIModelConfig...")
|
||||
test_api_model_config()
|
||||
print("✓ APIModelConfig works")
|
||||
|
||||
print("\nTesting experience operations...")
|
||||
test_experience_operations()
|
||||
print("✓ Experience operations work")
|
||||
|
||||
print("\nTesting apply_operations...")
|
||||
test_apply_operations()
|
||||
print("✓ Apply operations works")
|
||||
|
||||
if os.getenv("DEEPSEEK_API_KEY"):
|
||||
print("\nTesting DeepSeekAdapter (API key found)...")
|
||||
test_deepseek_adapter_basic()
|
||||
print("✓ DeepSeekAdapter works")
|
||||
|
||||
print("\nTesting generate_with_experiences...")
|
||||
test_generate_with_experiences()
|
||||
print("✓ Generate with experiences works")
|
||||
else:
|
||||
print("\n⚠ Skipping API tests (DEEPSEEK_API_KEY not set)")
|
||||
|
||||
print("\n✅ ALL TESTS PASSED!")
|
||||
Reference in New Issue
Block a user