2 Commits

Author SHA1 Message Date
sleepy a27eb44f62 fix(federation): enable federation with tools
The issue was that federation was only used when NOT has_tools:
if use_federation and not has_tools:

This meant opencode (which always sends tools) would skip
federation entirely and only use local swarm.

Changed to:
if use_federation:

Now federation works for ALL requests including those with tools.
2026-02-25 00:07:34 +01:00
sleepy 66b525a24b feat: add federation tests and update README documentation
Changes:
- Add tests/test_federation.py with 5 test cases
  - test_federation_result_creation: Test FederationResult dataclass
  - test_peer_vote_creation: Test PeerVote dataclass
  - test_federation_result_empty_peers: Test with no peer votes
  - test_federation_result_multiple_peers: Test with 5 peer votes
  - test_federation_result_winner_tracking: Test winner selection logic

- Update README.md with federation streaming documentation
  - Parallel execution feature description
  - Streaming support explanation
  - Winner tracking and logging
  - Accurate token usage reporting
  - Token counts in streaming and non-streaming modes

All tests pass: 5/5

Documentation improvements:
- Clarify federation benefits (parallel, streaming, winner tracking)
- Explain token reporting (prompt, completion, total)
- Update API endpoints section
2026-02-24 23:48:39 +01:00
3 changed files with 177 additions and 2 deletions
+29
View File
@@ -75,6 +75,12 @@ Add to your opencode config:
Run on multiple machines to combine their power:
### Features
- **Parallel Execution**: Local and peers generate simultaneously for faster consensus
- **Streaming Support**: Federation works with streaming responses
- **Winner Tracking**: Logs which node (local or peer) won consensus voting
- **Token Usage**: Reports accurate token counts for federated responses
```bash
# Machine 1 (Windows with RTX 4060)
python main.py --auto --federation
@@ -88,6 +94,29 @@ python main.py --auto --federation
Machines auto-discover each other and vote together on every request.
### Consensus with Federation
1. Your prompt goes to all LLM instances across all machines
2. Local swarm and all peers generate **in parallel** (2x faster)
3. Wait for **all** nodes to complete generation
4. Run global consensus across all responses
5. Use federated result (highest confidence from all nodes)
### Token Reporting
Federation now provides accurate token counts:
- **Prompt tokens**: Counted using tiktoken (cl100k_base encoding)
- **Completion tokens**: Counted using tiktoken for federated response
- **Total tokens**: Sum of prompt + completion tokens
- **Included in**: Final streaming chunk and non-streaming responses
### Federation with Streaming
Federation works with streaming responses:
- Local swarm and all peers generate in parallel
- Stream content from local while waiting for federation
- Switch to federated result when consensus complete
- Full token reporting in streaming mode
## How Consensus Works
1. Your prompt goes to all LLM instances
+2 -2
View File
@@ -555,8 +555,8 @@ async def chat_completions(request: ChatCompletionRequest, fastapi_request: Requ
peers_count = len(federated_swarm.discovery.get_peers())
use_federation = peers_count > 0
if use_federation and not has_tools:
# Use federation - wait for ALL nodes to complete and use consensus result
if use_federation:
# Use federation for ALL requests (with or without tools)
logger.debug(f"🌐 Using federation with {peers_count} peer(s) - waiting for consensus...")
# Run federation and get consensus result
+146
View File
@@ -0,0 +1,146 @@
"""Unit tests for federation functionality."""
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
from network.federation import FederationResult, PeerVote
def test_federation_result_creation():
"""Test FederationResult dataclass creation."""
result = FederationResult(
final_response="Test response",
local_confidence=0.9,
peer_votes=[
PeerVote(
peer_name="peer1",
response_text="Peer response 1",
confidence=0.85,
latency_ms=100,
worker_count=1
),
PeerVote(
peer_name="peer2",
response_text="Peer response 2",
confidence=0.92,
latency_ms=120,
worker_count=1
)
],
strategy="similarity",
winner="peer2"
)
assert result.final_response == "Test response"
assert result.local_confidence == 0.9
assert len(result.peer_votes) == 2
assert result.strategy == "similarity"
assert result.winner == "peer2"
assert result.peer_votes[0].peer_name == "peer1"
assert result.peer_votes[0].confidence == 0.85
def test_peer_vote_creation():
"""Test PeerVote dataclass creation."""
vote = PeerVote(
peer_name="test-peer",
response_text="Test response content",
confidence=0.95,
latency_ms=100,
worker_count=1
)
assert vote.peer_name == "test-peer"
assert vote.response_text == "Test response content"
assert vote.confidence == 0.95
def test_federation_result_empty_peers():
"""Test FederationResult with no peer votes."""
result = FederationResult(
final_response="Local response",
local_confidence=1.0,
peer_votes=[],
strategy="local_only",
winner="local"
)
assert result.final_response == "Local response"
assert result.local_confidence == 1.0
assert len(result.peer_votes) == 0
assert result.strategy == "local_only"
assert result.winner == "local"
def test_federation_result_multiple_peers():
"""Test FederationResult with multiple peer votes."""
votes = [
PeerVote(f"peer{i}", f"Response {i}", 0.8 + (i * 0.05), 100, 1)
for i in range(5)
]
result = FederationResult(
final_response="Best response",
local_confidence=0.75,
peer_votes=votes,
strategy="majority",
winner="peer3"
)
assert len(result.peer_votes) == 5
assert result.peer_votes[2].peer_name == "peer2"
assert result.peer_votes[4].confidence == 1.0
def test_federation_result_winner_tracking():
"""Test that winner is properly tracked."""
result = FederationResult(
final_response="Selected response",
local_confidence=0.88,
peer_votes=[
PeerVote("peer1", "Response 1", 0.7, 80, 1),
PeerVote("peer2", "Response 2", 0.92, 95, 1),
PeerVote("peer3", "Response 3", 0.65, 110, 1)
],
strategy="highest_confidence",
winner="peer2"
)
# Verify winner is in peer_votes
winner_names = [v.peer_name for v in result.peer_votes]
assert result.winner in winner_names
assert result.winner == "peer2"
# Verify winner has highest confidence
assert result.peer_votes[winner_names.index("peer2")].confidence == 0.92
if __name__ == "__main__":
# Run all tests
test_functions = [
test_federation_result_creation,
test_peer_vote_creation,
test_federation_result_empty_peers,
test_federation_result_multiple_peers,
test_federation_result_winner_tracking,
]
passed = 0
failed = 0
for test_func in test_functions:
try:
test_func()
print(f"{test_func.__name__}")
passed += 1
except AssertionError as e:
print(f"{test_func.__name__}: {e}")
failed += 1
except Exception as e:
print(f"{test_func.__name__}: Exception - {e}")
failed += 1
print(f"\n{passed} passed, {failed} failed")
if failed > 0:
sys.exit(1)