-
Notifications
You must be signed in to change notification settings - Fork 206
feat(integration-tests): add OpenAI integration tests and configuration #252
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| """ | ||
| Integration tests for OpenAgents demos and end-to-end scenarios. | ||
|
|
||
| This package contains tests that verify complete demo workflows, | ||
| including multi-agent communication and real LLM interactions. | ||
| """ |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,179 @@ | ||||||||||||||
| """ | ||||||||||||||
| Configuration and fixtures for integration tests. | ||||||||||||||
|
|
||||||||||||||
| Provides shared fixtures for: | ||||||||||||||
| - Network setup with dynamic port allocation | ||||||||||||||
| - Agent creation and lifecycle management | ||||||||||||||
| - Client connections for message sending | ||||||||||||||
| """ | ||||||||||||||
|
|
||||||||||||||
| import pytest | ||||||||||||||
| import asyncio | ||||||||||||||
| import os | ||||||||||||||
| from pathlib import Path | ||||||||||||||
| from typing import Tuple | ||||||||||||||
|
|
||||||||||||||
| # Load .env file from project root if it exists | ||||||||||||||
| try: | ||||||||||||||
| from dotenv import load_dotenv | ||||||||||||||
| env_file = Path(__file__).parent.parent.parent / ".env" | ||||||||||||||
| if env_file.exists(): | ||||||||||||||
| load_dotenv(env_file) | ||||||||||||||
| print(f"Loaded environment from {env_file}") | ||||||||||||||
| except ImportError: | ||||||||||||||
| pass # python-dotenv not installed | ||||||||||||||
|
|
||||||||||||||
| from openagents.core.network import create_network | ||||||||||||||
| from openagents.core.client import AgentClient | ||||||||||||||
| from openagents.launchers.network_launcher import load_network_config | ||||||||||||||
| from openagents.utils.port_allocator import get_port_pair, release_port, wait_for_port_free | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| def check_llm_api_key(): | ||||||||||||||
| """Check if an LLM API key is available for tests.""" | ||||||||||||||
| # Check for OpenAI key | ||||||||||||||
| if os.getenv("OPENAI_API_KEY"): | ||||||||||||||
| return True | ||||||||||||||
| # Check for auto model configuration | ||||||||||||||
| if os.getenv("DEFAULT_LLM_API_KEY"): | ||||||||||||||
| return True | ||||||||||||||
| return False | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| def skip_without_api_key(reason: str = "Requires LLM API key (OPENAI_API_KEY or DEFAULT_LLM_API_KEY)"): | ||||||||||||||
| """Skip decorator for tests requiring LLM API key.""" | ||||||||||||||
| return pytest.mark.skipif(not check_llm_api_key(), reason=reason) | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| @pytest.fixture | ||||||||||||||
| def hello_world_network_config_path() -> Path: | ||||||||||||||
| """Get path to the Hello World demo network configuration.""" | ||||||||||||||
| return Path(__file__).parent.parent.parent / "demos" / "00_hello_world" / "network.yaml" | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| @pytest.fixture | ||||||||||||||
| def hello_world_agent_config_path() -> Path: | ||||||||||||||
| """Get path to the Hello World demo Charlie agent configuration.""" | ||||||||||||||
| return Path(__file__).parent.parent.parent / "demos" / "00_hello_world" / "agents" / "charlie.yaml" | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| @pytest.fixture | ||||||||||||||
| async def hello_world_network(hello_world_network_config_path) -> Tuple: | ||||||||||||||
| """Create and start the Hello World network with dynamic ports. | ||||||||||||||
|
|
||||||||||||||
| Yields: | ||||||||||||||
| Tuple of (network, config, grpc_port, http_port) | ||||||||||||||
| """ | ||||||||||||||
| # Load config and use dynamic port allocation to avoid conflicts | ||||||||||||||
| config = load_network_config(str(hello_world_network_config_path)) | ||||||||||||||
|
|
||||||||||||||
| # Get two guaranteed free ports for gRPC and HTTP transports | ||||||||||||||
| grpc_port, http_port = get_port_pair() | ||||||||||||||
| print(f"Hello World network using ports: gRPC={grpc_port}, HTTP={http_port}") | ||||||||||||||
|
|
||||||||||||||
| # Update transport ports in config | ||||||||||||||
| for transport in config.network.transports: | ||||||||||||||
| if transport.type == "grpc": | ||||||||||||||
| transport.config["port"] = grpc_port | ||||||||||||||
| elif transport.type == "http": | ||||||||||||||
| transport.config["port"] = http_port | ||||||||||||||
|
|
||||||||||||||
| # Create and initialize network | ||||||||||||||
| network = create_network(config.network) | ||||||||||||||
|
|
||||||||||||||
| try: | ||||||||||||||
| await network.initialize() | ||||||||||||||
| print(f"Network initialized successfully on ports {grpc_port}, {http_port}") | ||||||||||||||
| except Exception as e: | ||||||||||||||
| print(f"Network initialization failed: {e}") | ||||||||||||||
| release_port(grpc_port) | ||||||||||||||
| release_port(http_port) | ||||||||||||||
| raise | ||||||||||||||
|
|
||||||||||||||
| # Give network time to start up | ||||||||||||||
| await asyncio.sleep(1.0) | ||||||||||||||
|
|
||||||||||||||
| # Verify network is ready with health check | ||||||||||||||
| max_retries = 10 | ||||||||||||||
| for attempt in range(max_retries): | ||||||||||||||
| try: | ||||||||||||||
| import aiohttp | ||||||||||||||
|
||||||||||||||
| async with aiohttp.ClientSession() as session: | ||||||||||||||
| try: | ||||||||||||||
| async with session.get(f"http://localhost:{http_port}/api/health", timeout=1) as resp: | ||||||||||||||
| if resp.status == 200: | ||||||||||||||
| print(f"Network health check passed on attempt {attempt + 1}") | ||||||||||||||
| break | ||||||||||||||
| except: | ||||||||||||||
| pass | ||||||||||||||
| except: | ||||||||||||||
|
Comment on lines
+107
to
+109
|
||||||||||||||
| except: | |
| pass | |
| except: | |
| except (aiohttp.ClientError, asyncio.TimeoutError): | |
| pass | |
| except Exception: |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using asyncio.create_task() with asyncio.to_thread() is redundant. The asyncio.gather() function already handles awaitable objects, and asyncio.to_thread() returns a coroutine that can be directly passed to gather. Simplify to: await asyncio.gather(asyncio.to_thread(wait_for_port_free, grpc_port, 'localhost', 5.0), asyncio.to_thread(wait_for_port_free, http_port, 'localhost', 5.0)).
| asyncio.create_task(asyncio.to_thread(wait_for_port_free, grpc_port, 'localhost', 5.0)), | |
| asyncio.create_task(asyncio.to_thread(wait_for_port_free, http_port, 'localhost', 5.0)) | |
| asyncio.to_thread(wait_for_port_free, grpc_port, 'localhost', 5.0), | |
| asyncio.to_thread(wait_for_port_free, http_port, 'localhost', 5.0), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Print statement may execute during import.