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:
2025-10-28 21:14:24 -07:00
parent 6ad1592dc3
commit 474ccac8a5
54 changed files with 4038 additions and 10916 deletions
+320
View File
@@ -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()
+236
View File
@@ -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!")