diff --git a/__init__.py b/__init__.py deleted file mode 100644 index 93de2e624..000000000 --- a/__init__.py +++ /dev/null @@ -1,36 +0,0 @@ -from importlib.metadata import version -from pathlib import Path -from typing import Any - -from loguru import logger - -__version__ = version("apex") - - -def setup_logger(log_file_path: str | Path | None = None, level: str = "INFO") -> Any: - """Set up the loguru logger with optional file logging and specified log level. - - Args: - log_file_path: Path to the log file. If None, logs won't be saved to a file. - level: Logging level (e.g., "INFO", "DEBUG", "ERROR"). Defaults to "INFO". - """ - # Remove existing handlers to avoid duplicate logs. - logger.remove() - - # Console handler format. - log_format = ( - "{time:YYYY-MM-DD HH:mm:ss} [{file}:{line}] {message}" - ) - - # Add console handler. - logger.add(lambda msg: print(msg), level=level, format=log_format) - - # Add file handler if a path is provided. - if log_file_path: - file_log_format = "{time:YYYY-MM-DD HH:mm:ss} [{file}:{line}] {message}" - logger.add(str(log_file_path), level=level, format=file_log_format, rotation="10 MB", retention="7 days") - - return logger - - -setup_logger(level="DEBUG") diff --git a/apex/common/epistula.py b/apex/common/epistula.py new file mode 100644 index 000000000..ff72b4f28 --- /dev/null +++ b/apex/common/epistula.py @@ -0,0 +1,8 @@ +import json +from typing import Any + +import bittensor as bt + + +def generate_header(hotkey: bt.wallet.hotkey, body: dict[Any, Any] | list[Any]) -> dict[str, Any]: + return {"Body-Signature": "0x" + hotkey.sign(json.dumps(body)).hex()} diff --git a/apex/validator/miner_sampler.py b/apex/validator/miner_sampler.py index 5202fe2fe..651cecf23 100644 --- a/apex/validator/miner_sampler.py +++ b/apex/validator/miner_sampler.py @@ -11,6 +11,7 @@ from apex.common.async_chain import AsyncChain from apex.common.constants import VALIDATOR_REFERENCE_LABEL +from apex.common.epistula import generate_header from apex.common.models import MinerDiscriminatorResults, MinerGeneratorResults from apex.common.utils import async_cache from apex.validator.logger_db import LoggerDB @@ -127,9 +128,16 @@ async def _sample_miners(self) -> list[MinerInfo]: async def query_miners(self, body: dict[str, Any], endpoint: str) -> str: """Query the miners for the query.""" + body["signer"] = await self._chain.wallet.hotkey.ss58_address + body["signed_for"] = endpoint + body["nonce"] = str(int(time.time())) try: async with aiohttp.ClientSession() as session: - async with session.post(endpoint + "/v1/chat/completions", json=body) as resp: + async with session.post( + endpoint + "/v1/chat/completions", + headers=generate_header(self._chain.wallet.hotkey, body), + json=body, + ) as resp: result = await resp.text() except BaseException: # Error during miner query, return empty string. diff --git a/config/mainnet.yaml.example b/config/mainnet.yaml.example index 9a68b573a..3d8bffaf6 100644 --- a/config/mainnet.yaml.example +++ b/config/mainnet.yaml.example @@ -27,4 +27,3 @@ deep_research: research_model: "Qwen/Qwen3-235B-A22B-Instruct-2507" compression_model: "deepseek-ai/DeepSeek-V3-0324" final_model: "deepseek-ai/DeepSeek-V3-0324" - diff --git a/docs/incentive-mechanism.md b/docs/incentive-mechanism.md index 5235db979..6f35b1ead 100644 --- a/docs/incentive-mechanism.md +++ b/docs/incentive-mechanism.md @@ -52,7 +52,7 @@ The ground truth system works as follows: When the validator generates references, it uses a sophisticated deep research process: ``` -System Prompt: "You are Deep Researcher, a meticulous assistant. For each claim you make, +System Prompt: "You are Deep Researcher, a meticulous assistant. For each claim you make, provide step-by-step reasoning and cite exact source numbers from the provided context." Process: Research Question → Web Search → LLM Analysis → Cited Response @@ -98,14 +98,14 @@ The system maintains a 22-hour rolling window for score aggregation: **Objective**: Produce responses that are indistinguishable from high-quality validator references -**Strategy**: +**Strategy**: - Generate human-like, well-reasoned responses - Match the style and quality of validator deep research outputs - Avoid patterns that discriminators can easily identify as miner-generated **Reward**: Higher scores when discriminators fail to correctly classify their output as miner-generated -### For Discriminators +### For Discriminators **Objective**: Accurately distinguish between miner-generated and validator-generated content @@ -149,7 +149,7 @@ Miners receive requests through a standardized API: **Discriminator Request**: ```json { - "step": "discriminator", + "step": "discriminator", "query": "Input query", "generation": "Response to classify" } @@ -164,7 +164,7 @@ Miners receive requests through a standardized API: The validator uses a `MinerSampler` that: - Samples miners from the network metagraph -- Supports multiple sampling modes (random, sequential) +- Supports multiple sampling modes (random, sequential) - Queries multiple miners simultaneously for each task - Tracks miner information and performance metrics @@ -203,7 +203,7 @@ The current codebase includes a dummy miner implementation that: For effective participation, miners must implement: 1. **Advanced Generation**: High-quality language models or API integrations -2. **Sophisticated Discrimination**: ML models or heuristics for content classification +2. **Sophisticated Discrimination**: ML models or heuristics for content classification 3. **Robust Error Handling**: Network failures, malformed requests, etc. 4. **Performance Optimization**: Fast response times to maximize participation @@ -211,4 +211,4 @@ For effective participation, miners must implement: The Apex incentive mechanism creates a sophisticated competitive environment that drives continuous improvement in both content generation and quality assessment. By requiring miners to excel in both generator and discriminator roles, the system ensures that network participants develop comprehensive AI capabilities while maintaining high standards for content quality. -The zero-sum competition between generators and discriminators, combined with the high-quality validator references, creates powerful incentives for miners to approach or exceed validator-level performance, ultimately benefiting the entire network with increasingly sophisticated AI capabilities. \ No newline at end of file +The zero-sum competition between generators and discriminators, combined with the high-quality validator references, creates powerful incentives for miners to approach or exceed validator-level performance, ultimately benefiting the entire network with increasingly sophisticated AI capabilities. diff --git a/docs/miner.md b/docs/miner.md index c9d2995f8..42491567f 100644 --- a/docs/miner.md +++ b/docs/miner.md @@ -158,4 +158,4 @@ The `MinerScorer` evaluates miner performance: 1. **Port Already in Use**: Change the `--port` parameter 2. **Wallet Not Found**: Verify coldkey and hotkey names 3. **Network Connection**: Check internet connectivity and firewall settings -4. **Subnet Registration**: Ensure sufficient balance for registration fees \ No newline at end of file +4. **Subnet Registration**: Ensure sufficient balance for registration fees diff --git a/py.typed b/py.typed index 0519ecba6..e69de29bb 100644 --- a/py.typed +++ b/py.typed @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index f08fcfd7f..7de756a48 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -166,6 +166,12 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" [tool.ruff.lint.pydocstyle] convention = "google" +[tool.ruff.lint.isort] +# Sort by module, then by case-sensitive name. +# See https://docs.astral.sh/ruff/settings/#lint-isort-order-by-type +order-by-type = true + + [tool.ruff.format] # Like Black, use double quotes for strings. quote-style = "double" @@ -183,7 +189,7 @@ apex-deep-research = { git = "https://github.com/macrocosm-os/apex_deep_research override-dependencies = [ # Override artificial pins from bittensor ecosystem for production "starlette>=0.37.2", - "aiohttp>=3.8.3", + "aiohttp>=3.8.3", "protobuf>=3.20.2", "cachetools>=2.0.0", ] diff --git a/tests/validator/test_miner_sampler.py b/tests/validator/test_miner_sampler.py index 2bb9cb5ca..4f00e9ee3 100644 --- a/tests/validator/test_miner_sampler.py +++ b/tests/validator/test_miner_sampler.py @@ -184,12 +184,14 @@ async def test_sample_miners_sequential(monkeypatch: MagicMock, miner_sampler: M @pytest.mark.asyncio async def test_query_miners() -> None: """Tests that a query to a miner is successful.""" - sampler = MinerSampler(chain=MagicMock()) # type: ignore + mock_chain = MagicMock() + mock_chain.wallet.hotkey.ss58_address = "test_address" + sampler = MinerSampler(chain=mock_chain) # type: ignore endpoint = "http://test.com" body = {"test": "data"} mock_resp = AsyncMock() - mock_resp.text.return_value = '{"response": "ok"}' + mock_resp.text = AsyncMock(return_value='{"response": "ok"}') mock_session_post = MagicMock() mock_session_post.__aenter__.return_value = mock_resp @@ -200,11 +202,20 @@ async def test_query_miners() -> None: mock_session.__aenter__.return_value = mock_session mock_session.__aexit__.return_value = None - with patch("aiohttp.ClientSession", return_value=mock_session) as mock_client_session: + with ( + patch("aiohttp.ClientSession", return_value=mock_session) as mock_client_session, + patch("apex.validator.miner_sampler.generate_header") as mock_generate_header, + patch("time.time", return_value=12345), + ): + mock_generate_header.return_value = {"some": "header"} result = await sampler.query_miners(body, endpoint) mock_client_session.assert_called_once() - mock_session.post.assert_called_with(endpoint + "/v1/chat/completions", json=body) + expected_body = {"test": "data", "signer": "test_address", "signed_for": "http://test.com", "nonce": "12345"} + mock_session.post.assert_called_with( + endpoint + "/v1/chat/completions", headers={"some": "header"}, json=expected_body + ) + mock_generate_header.assert_called_with(mock_chain.wallet.hotkey, expected_body) assert result == '{"response": "ok"}' diff --git a/validator.py b/validator.py index e3608ba4f..754afa9a0 100644 --- a/validator.py +++ b/validator.py @@ -20,9 +20,8 @@ async def read_args() -> argparse.Namespace: parser.add_argument( "-c", "--config", - # required=True, - # default="config/mainnet.yaml", - default="config/testnet.yaml", + # default="config/testnet.yaml", + default="config/mainnet.yaml", help="Config file path (e.g. config/mainnet.yaml).", type=Path, )