hypermind 0.11.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hmctl/__init__.py +7 -0
- hmctl/cli.py +947 -0
- hypermind/__init__.py +208 -0
- hypermind/_deprecations.py +108 -0
- hypermind/agent.py +2896 -0
- hypermind/agent_card.py +127 -0
- hypermind/api/__init__.py +32 -0
- hypermind/api/app.py +686 -0
- hypermind/capability.py +923 -0
- hypermind/consult.py +493 -0
- hypermind/consult_bias.py +112 -0
- hypermind/contract_net.py +219 -0
- hypermind/crypto/__init__.py +21 -0
- hypermind/crypto/cose_encrypt.py +126 -0
- hypermind/crypto/dkg.py +246 -0
- hypermind/crypto/domain.py +59 -0
- hypermind/crypto/frost.py +771 -0
- hypermind/crypto/hashing.py +38 -0
- hypermind/crypto/hlc.py +163 -0
- hypermind/crypto/hpke.py +277 -0
- hypermind/crypto/kms.py +196 -0
- hypermind/crypto/kms_backends/__init__.py +56 -0
- hypermind/crypto/kms_backends/aws.py +128 -0
- hypermind/crypto/kms_backends/azure.py +114 -0
- hypermind/crypto/kms_backends/gcp.py +97 -0
- hypermind/crypto/kms_backends/vault.py +120 -0
- hypermind/crypto/namespace_root.py +204 -0
- hypermind/crypto/pq.py +152 -0
- hypermind/crypto/room_epoch.py +97 -0
- hypermind/crypto/signing.py +245 -0
- hypermind/deliberation.py +355 -0
- hypermind/did_web.py +256 -0
- hypermind/dispute.py +858 -0
- hypermind/errors.py +218 -0
- hypermind/eval/__init__.py +49 -0
- hypermind/eval/calibration.py +307 -0
- hypermind/eval/comparison.py +251 -0
- hypermind/eval/replay.py +202 -0
- hypermind/eval/swarm_iq.py +718 -0
- hypermind/knowledge/__init__.py +17 -0
- hypermind/knowledge/citation_graph.py +141 -0
- hypermind/knowledge/correlated_agreement.py +167 -0
- hypermind/knowledge/embedding_registry.py +115 -0
- hypermind/knowledge/kernel_store.py +197 -0
- hypermind/knowledge/rekor.py +173 -0
- hypermind/knowledge/reputation.py +364 -0
- hypermind/knowledge/swarm_memory.py +523 -0
- hypermind/knowledge/transparency.py +409 -0
- hypermind/mcp/__init__.py +60 -0
- hypermind/mcp/bridge.py +151 -0
- hypermind/mcp/client.py +142 -0
- hypermind/mcp/provenance.py +214 -0
- hypermind/mcp/relata_tools.py +152 -0
- hypermind/mcp/server.py +248 -0
- hypermind/mcp/transport.py +206 -0
- hypermind/mind/__init__.py +150 -0
- hypermind/mind/active.py +234 -0
- hypermind/mind/belief.py +369 -0
- hypermind/mind/deliberator.py +625 -0
- hypermind/mind/diversity.py +144 -0
- hypermind/mind/evolve.py +184 -0
- hypermind/mind/federation.py +154 -0
- hypermind/mind/goals.py +117 -0
- hypermind/mind/integration.py +190 -0
- hypermind/mind/mediator.py +74 -0
- hypermind/mind/memory_store.py +129 -0
- hypermind/mind/policy.py +307 -0
- hypermind/mind/records.py +260 -0
- hypermind/mind/roles.py +190 -0
- hypermind/mind/sybil_guard.py +41 -0
- hypermind/mind/world_model.py +166 -0
- hypermind/namespace.py +515 -0
- hypermind/namespace_policy.py +254 -0
- hypermind/observability/__init__.py +25 -0
- hypermind/observability/asgi.py +214 -0
- hypermind/observability/cost.py +277 -0
- hypermind/observability/otel_export.py +200 -0
- hypermind/observability/recorder.py +344 -0
- hypermind/observability/swarm_trace.py +438 -0
- hypermind/pin_policy.py +116 -0
- hypermind/py.typed +0 -0
- hypermind/responders/__init__.py +87 -0
- hypermind/responders/anthropic.py +159 -0
- hypermind/responders/base.py +162 -0
- hypermind/responders/openai.py +199 -0
- hypermind/responders/router.py +254 -0
- hypermind/responders/structured.py +287 -0
- hypermind/responders/tools.py +444 -0
- hypermind/revocation.py +270 -0
- hypermind/rule_ids.py +135 -0
- hypermind/schemas/encrypted_statement.cddl +28 -0
- hypermind/schemas/namespace_accept.cddl +20 -0
- hypermind/schemas/namespace_create.cddl +25 -0
- hypermind/schemas/namespace_founding_attest.cddl +30 -0
- hypermind/schemas/namespace_invite.cddl +22 -0
- hypermind/schemas/wire-v0.1.cddl +73 -0
- hypermind/simlab/__init__.py +15 -0
- hypermind/simlab/_persona_registry.py +93 -0
- hypermind/simlab/_scenario_impl.py +2778 -0
- hypermind/simlab/app.py +2538 -0
- hypermind/simlab/backends/__init__.py +27 -0
- hypermind/simlab/backends/anchor.py +88 -0
- hypermind/simlab/backends/local.py +32 -0
- hypermind/simlab/backends/server.py +79 -0
- hypermind/simlab/cli_command.py +108 -0
- hypermind/simlab/config.py +106 -0
- hypermind/simlab/deployment.py +26 -0
- hypermind/simlab/knowledge.py +716 -0
- hypermind/simlab/learning.py +295 -0
- hypermind/simlab/modes/__init__.py +115 -0
- hypermind/simlab/modes/_eval_real_llm.py +373 -0
- hypermind/simlab/modes/aar.py +611 -0
- hypermind/simlab/modes/backtest.py +610 -0
- hypermind/simlab/modes/binary_forecast.py +69 -0
- hypermind/simlab/modes/conformance.py +1869 -0
- hypermind/simlab/modes/evaluation.py +2271 -0
- hypermind/simlab/modes/governance.py +648 -0
- hypermind/simlab/modes/redteam.py +534 -0
- hypermind/simlab/modes/tabletop.py +797 -0
- hypermind/simlab/modes/tournament.py +448 -0
- hypermind/simlab/modes/whatif.py +523 -0
- hypermind/simlab/namespace.py +125 -0
- hypermind/simlab/personas.py +477 -0
- hypermind/simlab/prompt_generator.py +172 -0
- hypermind/simlab/question_sets/geopolitics_q20.json +122 -0
- hypermind/simlab/question_sets/governance_q5.json +67 -0
- hypermind/simlab/question_sets/msft_outlook_q10.json +62 -0
- hypermind/simlab/question_sets/whatif_q3.json +38 -0
- hypermind/simlab/registry.py +325 -0
- hypermind/simlab/runner.py +239 -0
- hypermind/simlab/scenario.py +147 -0
- hypermind/simlab/swarms.py +180 -0
- hypermind/simlab/tool_handlers/__init__.py +4 -0
- hypermind/simlab/tool_handlers/alpha_vantage.py +39 -0
- hypermind/simlab/tool_handlers/brave.py +46 -0
- hypermind/simlab/tool_handlers/newsapi.py +50 -0
- hypermind/simlab/tool_handlers/openweather.py +46 -0
- hypermind/simlab/tool_handlers/pinecone.py +52 -0
- hypermind/simlab/tool_handlers/qdrant.py +57 -0
- hypermind/simlab/tool_handlers/query_knowledge_base.py +21 -0
- hypermind/simlab/tool_handlers/relata_recall.py +61 -0
- hypermind/simlab/tool_handlers/retrieve_document.py +21 -0
- hypermind/simlab/tool_handlers/serper.py +46 -0
- hypermind/simlab/tool_handlers/tavily.py +53 -0
- hypermind/simlab/tool_handlers/weaviate.py +65 -0
- hypermind/simlab/tool_handlers/wikipedia.py +64 -0
- hypermind/simlab/tool_handlers/wolfram.py +48 -0
- hypermind/simlab/tool_handlers/yahoo_finance.py +49 -0
- hypermind/simlab/tool_registry.py +568 -0
- hypermind/simlab/topic_adapter.py +338 -0
- hypermind/simlab/topic_questions.py +259 -0
- hypermind/storage/__init__.py +69 -0
- hypermind/storage/backend.py +71 -0
- hypermind/storage/relata.py +245 -0
- hypermind/storage/sqlite.py +258 -0
- hypermind/sync.py +191 -0
- hypermind/tal.py +175 -0
- hypermind/tasks.py +334 -0
- hypermind/testing.py +16 -0
- hypermind/tools/__init__.py +32 -0
- hypermind/tools/registry.py +61 -0
- hypermind/transport/__init__.py +22 -0
- hypermind/transport/anti_entropy.py +708 -0
- hypermind/transport/bus.py +206 -0
- hypermind/transport/knows_delta.py +204 -0
- hypermind/transport/libp2p.py +298 -0
- hypermind/transport/placement.py +58 -0
- hypermind/transport/tcp.py +797 -0
- hypermind/transport/tls_profile.py +417 -0
- hypermind/types.py +59 -0
- hypermind/uncertainty.py +222 -0
- hypermind/wire.py +897 -0
- hypermind/workflow/__init__.py +72 -0
- hypermind/workflow/engine.py +655 -0
- hypermind/workflow/plan.py +182 -0
- hypermind/workflow/repair.py +203 -0
- hypermind-0.11.0.dist-info/METADATA +524 -0
- hypermind-0.11.0.dist-info/RECORD +181 -0
- hypermind-0.11.0.dist-info/WHEEL +4 -0
- hypermind-0.11.0.dist-info/entry_points.txt +2 -0
- hypermind-0.11.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
"""Real-LLM forecaster integration for the Evaluation Bench.
|
|
2
|
+
|
|
3
|
+
Replaces the deterministic-synthetic posteriors with actual OpenRouter
|
|
4
|
+
calls when ``use_real_llm`` is set on the mode payload AND the
|
|
5
|
+
``OPENROUTER_API_KEY`` environment variable is present.
|
|
6
|
+
|
|
7
|
+
Architecture:
|
|
8
|
+
|
|
9
|
+
* Three model-based forecasters (HyperMind 4-role panel, Single-LLM,
|
|
10
|
+
Naive ensemble of N=4 temperature-jittered calls). The 3 honest
|
|
11
|
+
baselines (Random, Majority, Climatology) stay deterministic — they
|
|
12
|
+
have no model.
|
|
13
|
+
* Per-question cache keyed by content hash so repeat runs of the same
|
|
14
|
+
benchmark don't re-pay for the same questions. Cache lives at
|
|
15
|
+
``~/.hypermind/eval_llm_cache/``.
|
|
16
|
+
* :class:`RunCost` captures token usage + USD cost per forecaster so the
|
|
17
|
+
evaluation report can show the *cost-adjusted* skill score (HyperMind's
|
|
18
|
+
4× call count vs Single-LLM's 1× — is the lift worth the spend?).
|
|
19
|
+
* Falls back gracefully to synthetic when:
|
|
20
|
+
- no API key is present
|
|
21
|
+
- the OpenAI client import fails
|
|
22
|
+
- any individual call errors out (single forecaster degrades; the
|
|
23
|
+
rest of the run continues)
|
|
24
|
+
|
|
25
|
+
Reuses :func:`hypermind.simlab._scenario_impl._make_openrouter_client`
|
|
26
|
+
to avoid re-implementing the OpenRouter handshake.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import asyncio
|
|
32
|
+
import hashlib
|
|
33
|
+
import json
|
|
34
|
+
import os
|
|
35
|
+
import time
|
|
36
|
+
from dataclasses import asdict, dataclass
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
from typing import Any
|
|
39
|
+
|
|
40
|
+
# --- Cost capture ----------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class RunCost:
|
|
45
|
+
"""Per-forecaster aggregate cost for a benchmark run.
|
|
46
|
+
|
|
47
|
+
Surfaces in the evaluation report so a reviewer sees not just
|
|
48
|
+
'HyperMind beat Single-LLM by 12% on Brier' but also 'and it cost
|
|
49
|
+
4.1× as many tokens to do so'. This is the fair comparison.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
n_calls: int = 0
|
|
53
|
+
total_tokens_prompt: int = 0
|
|
54
|
+
total_tokens_completion: int = 0
|
|
55
|
+
estimated_usd: float = 0.0
|
|
56
|
+
elapsed_s: float = 0.0
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# --- Cache ----------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
CACHE_DIR = Path(os.path.expanduser("~/.hypermind/eval_llm_cache"))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _cache_key(model: str, prompt: str, temperature: float) -> str:
|
|
65
|
+
canonical = f"{model}::{temperature:.2f}::{prompt}"
|
|
66
|
+
return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _cache_get(key: str) -> dict[str, Any] | None:
|
|
70
|
+
path = CACHE_DIR / f"{key}.json"
|
|
71
|
+
if not path.exists():
|
|
72
|
+
return None
|
|
73
|
+
try:
|
|
74
|
+
return json.loads(path.read_text())
|
|
75
|
+
except Exception:
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _cache_set(key: str, value: dict[str, Any]) -> None:
|
|
80
|
+
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
81
|
+
path = CACHE_DIR / f"{key}.json"
|
|
82
|
+
try:
|
|
83
|
+
path.write_text(json.dumps(value))
|
|
84
|
+
except Exception:
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# --- Pricing (rough; conservative) ----------------------------------------
|
|
89
|
+
# Per-1M-token pricing in USD for OpenRouter passthrough. These are
|
|
90
|
+
# illustrative — for real cost reporting, callers should override with
|
|
91
|
+
# the price table their account actually pays.
|
|
92
|
+
_PRICE_PER_1M_TOKENS_USD: dict[str, tuple[float, float]] = {
|
|
93
|
+
# model-name : (prompt $/1M, completion $/1M)
|
|
94
|
+
"openai/gpt-4o-mini": (0.15, 0.60),
|
|
95
|
+
"openai/gpt-4o": (2.50, 10.00),
|
|
96
|
+
"anthropic/claude-3-5-haiku": (0.80, 4.00),
|
|
97
|
+
"anthropic/claude-3-5-sonnet": (3.00, 15.00),
|
|
98
|
+
"google/gemini-flash-1.5": (0.075, 0.30),
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _estimate_cost_usd(model: str, prompt_tokens: int, completion_tokens: int) -> float:
|
|
103
|
+
rate = _PRICE_PER_1M_TOKENS_USD.get(model)
|
|
104
|
+
if rate is None:
|
|
105
|
+
return 0.0
|
|
106
|
+
p_per_1m, c_per_1m = rate
|
|
107
|
+
return (prompt_tokens * p_per_1m + completion_tokens * c_per_1m) / 1_000_000
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# --- Single-call helper ---------------------------------------------------
|
|
111
|
+
|
|
112
|
+
_SYSTEM_PROMPT_NEUTRAL = (
|
|
113
|
+
"You are a careful probabilistic forecaster. For the question below, "
|
|
114
|
+
"produce your subjective posterior probability that the answer is YES "
|
|
115
|
+
"(actual=1). Calibrate honestly: a 70% answer should be right ~70% of "
|
|
116
|
+
"the time. Reply ONLY with a JSON object of the form "
|
|
117
|
+
'{"confidence": <number in [0,1]>, "reasoning": "<one sentence>"}.'
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
_SYSTEM_PROMPTS_BY_ROLE = {
|
|
121
|
+
"scout": (
|
|
122
|
+
"You are the SCOUT in a forecasting panel. Your job is to find and "
|
|
123
|
+
"weigh evidence. Lean toward the side the evidence supports. "
|
|
124
|
+
"Calibrate honestly. Reply ONLY with JSON: "
|
|
125
|
+
'{"confidence": <0..1>, "reasoning": "<sentence>"}.'
|
|
126
|
+
),
|
|
127
|
+
"sceptic": (
|
|
128
|
+
"You are the SCEPTIC in a forecasting panel. Your job is to "
|
|
129
|
+
"challenge over-confident claims. Pull confident posteriors back "
|
|
130
|
+
"toward 0.5 unless the evidence is strong. Reply ONLY with JSON: "
|
|
131
|
+
'{"confidence": <0..1>, "reasoning": "<sentence>"}.'
|
|
132
|
+
),
|
|
133
|
+
"synthesist": (
|
|
134
|
+
"You are the SYNTHESIST in a forecasting panel. Your job is to "
|
|
135
|
+
"balance the Scout's evidence and the Sceptic's caution. Aim for "
|
|
136
|
+
"calibrated middle-ground. Reply ONLY with JSON: "
|
|
137
|
+
'{"confidence": <0..1>, "reasoning": "<sentence>"}.'
|
|
138
|
+
),
|
|
139
|
+
"mediator": (
|
|
140
|
+
"You are the MEDIATOR in a forecasting panel. Your job is to "
|
|
141
|
+
"calibrate against the base rate. When the question is ambiguous, "
|
|
142
|
+
"pull the posterior toward 0.5. Reply ONLY with JSON: "
|
|
143
|
+
'{"confidence": <0..1>, "reasoning": "<sentence>"}.'
|
|
144
|
+
),
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
async def _llm_call(
|
|
149
|
+
client: Any,
|
|
150
|
+
model: str,
|
|
151
|
+
system: str,
|
|
152
|
+
question: str,
|
|
153
|
+
temperature: float,
|
|
154
|
+
run_cost: RunCost,
|
|
155
|
+
use_cache: bool = True,
|
|
156
|
+
) -> float:
|
|
157
|
+
"""One OpenRouter call returning a posterior in [0.05, 0.95].
|
|
158
|
+
|
|
159
|
+
Caches by (model, temperature, system+question) hash. Updates the
|
|
160
|
+
given RunCost in place (token + USD totals; n_calls; elapsed_s).
|
|
161
|
+
On any error returns 0.5 (uncertainty default) and records 0 cost.
|
|
162
|
+
"""
|
|
163
|
+
prompt = f"{system}\n\nQuestion: {question}"
|
|
164
|
+
key = _cache_key(model, prompt, temperature)
|
|
165
|
+
if use_cache:
|
|
166
|
+
cached = _cache_get(key)
|
|
167
|
+
if cached is not None:
|
|
168
|
+
run_cost.n_calls += 1 # logical call (cached)
|
|
169
|
+
return float(cached.get("confidence", 0.5))
|
|
170
|
+
|
|
171
|
+
started = time.time()
|
|
172
|
+
try:
|
|
173
|
+
resp = await client.chat.completions.create(
|
|
174
|
+
model=model,
|
|
175
|
+
messages=[
|
|
176
|
+
{"role": "system", "content": system},
|
|
177
|
+
{"role": "user", "content": question},
|
|
178
|
+
],
|
|
179
|
+
response_format={"type": "json_object"},
|
|
180
|
+
temperature=temperature,
|
|
181
|
+
timeout=30,
|
|
182
|
+
)
|
|
183
|
+
except Exception:
|
|
184
|
+
# Silent fallback to uncertainty default. The Limitations panel
|
|
185
|
+
# will already note synthetic-fallback when this happens broadly.
|
|
186
|
+
return 0.5
|
|
187
|
+
elapsed = time.time() - started
|
|
188
|
+
|
|
189
|
+
content = (resp.choices[0].message.content or "{}") if resp.choices else "{}"
|
|
190
|
+
try:
|
|
191
|
+
data = json.loads(content)
|
|
192
|
+
conf = float(data.get("confidence", 0.5))
|
|
193
|
+
except Exception:
|
|
194
|
+
conf = 0.5
|
|
195
|
+
conf = max(0.05, min(0.95, conf))
|
|
196
|
+
|
|
197
|
+
# Update cost tally.
|
|
198
|
+
usage = getattr(resp, "usage", None)
|
|
199
|
+
p_t = int(getattr(usage, "prompt_tokens", 0) or 0) if usage else 0
|
|
200
|
+
c_t = int(getattr(usage, "completion_tokens", 0) or 0) if usage else 0
|
|
201
|
+
run_cost.n_calls += 1
|
|
202
|
+
run_cost.total_tokens_prompt += p_t
|
|
203
|
+
run_cost.total_tokens_completion += c_t
|
|
204
|
+
run_cost.estimated_usd += _estimate_cost_usd(model, p_t, c_t)
|
|
205
|
+
run_cost.elapsed_s += elapsed
|
|
206
|
+
|
|
207
|
+
if use_cache:
|
|
208
|
+
_cache_set(key, {"confidence": conf})
|
|
209
|
+
return conf
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# --- Real-LLM forecasters -------------------------------------------------
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
async def real_hypermind_posterior(
|
|
216
|
+
client: Any,
|
|
217
|
+
model: str,
|
|
218
|
+
question: str,
|
|
219
|
+
run_cost: RunCost,
|
|
220
|
+
) -> tuple[float, dict[str, float]]:
|
|
221
|
+
"""Real HyperMind: 4 parallel role calls, anti-herding-aggregated.
|
|
222
|
+
|
|
223
|
+
Mirrors the synthetic version's logic: get 4 role posteriors, take
|
|
224
|
+
the mean, apply a small calibration pull toward 0.5 when the panel
|
|
225
|
+
disagrees on hard items. Returns (consensus, {role: posterior}).
|
|
226
|
+
"""
|
|
227
|
+
roles = ["scout", "sceptic", "synthesist", "mediator"]
|
|
228
|
+
coros = [
|
|
229
|
+
_llm_call(
|
|
230
|
+
client, model, _SYSTEM_PROMPTS_BY_ROLE[r], question, temperature=0.4, run_cost=run_cost
|
|
231
|
+
)
|
|
232
|
+
for r in roles
|
|
233
|
+
]
|
|
234
|
+
posts = await asyncio.gather(*coros)
|
|
235
|
+
role_posts = {r: p for r, p in zip(roles, posts, strict=True)}
|
|
236
|
+
mean = sum(posts) / len(posts)
|
|
237
|
+
var = sum((p - mean) ** 2 for p in posts) / len(posts)
|
|
238
|
+
stdev = var**0.5
|
|
239
|
+
distance = abs(mean - 0.5)
|
|
240
|
+
if stdev > 0.06 and distance < 0.20:
|
|
241
|
+
# Hard-question + high disagreement → calibrate toward 0.5.
|
|
242
|
+
mean = mean * 0.7 + 0.5 * 0.3
|
|
243
|
+
return max(0.05, min(0.95, mean)), role_posts
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
async def real_single_llm_posterior(
|
|
247
|
+
client: Any,
|
|
248
|
+
model: str,
|
|
249
|
+
question: str,
|
|
250
|
+
run_cost: RunCost,
|
|
251
|
+
) -> float:
|
|
252
|
+
"""Real Single-LLM: one neutral call, no role bias, no calibration."""
|
|
253
|
+
return await _llm_call(
|
|
254
|
+
client,
|
|
255
|
+
model,
|
|
256
|
+
_SYSTEM_PROMPT_NEUTRAL,
|
|
257
|
+
question,
|
|
258
|
+
temperature=0.4,
|
|
259
|
+
run_cost=run_cost,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
async def real_naive_ensemble_posterior(
|
|
264
|
+
client: Any,
|
|
265
|
+
model: str,
|
|
266
|
+
question: str,
|
|
267
|
+
run_cost: RunCost,
|
|
268
|
+
) -> float:
|
|
269
|
+
"""Real Naive ensemble: 4 calls at temperatures 0.3 / 0.5 / 0.7 / 0.9, averaged."""
|
|
270
|
+
temps = [0.3, 0.5, 0.7, 0.9]
|
|
271
|
+
coros = [_llm_call(client, model, _SYSTEM_PROMPT_NEUTRAL, question, t, run_cost) for t in temps]
|
|
272
|
+
posts = await asyncio.gather(*coros)
|
|
273
|
+
return sum(posts) / len(posts)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
# --- Public entry point ---------------------------------------------------
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def is_real_llm_available() -> bool:
|
|
280
|
+
"""True iff OPENROUTER_API_KEY is set AND the openai client imports."""
|
|
281
|
+
if not os.environ.get("OPENROUTER_API_KEY", "").strip():
|
|
282
|
+
return False
|
|
283
|
+
try:
|
|
284
|
+
from openai import AsyncOpenAI # noqa: F401
|
|
285
|
+
except ImportError:
|
|
286
|
+
return False
|
|
287
|
+
return True
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def make_real_client() -> Any | None:
|
|
291
|
+
"""Build an OpenRouter-pointed AsyncOpenAI client. Returns None on failure.
|
|
292
|
+
|
|
293
|
+
Reuses the helper from :mod:`hypermind.simlab._scenario_impl` so we
|
|
294
|
+
don't duplicate the headers + base URL plumbing.
|
|
295
|
+
"""
|
|
296
|
+
api_key = os.environ.get("OPENROUTER_API_KEY", "").strip()
|
|
297
|
+
if not api_key:
|
|
298
|
+
return None
|
|
299
|
+
try:
|
|
300
|
+
from .._scenario_impl import _make_openrouter_client
|
|
301
|
+
except ImportError:
|
|
302
|
+
return None
|
|
303
|
+
try:
|
|
304
|
+
return _make_openrouter_client(api_key)
|
|
305
|
+
except Exception:
|
|
306
|
+
return None
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
async def run_real_llm_forecasters(
|
|
310
|
+
questions: list[tuple[str, int]],
|
|
311
|
+
model: str,
|
|
312
|
+
) -> dict[str, Any] | None:
|
|
313
|
+
"""Run all 3 model-based forecasters against `questions` via OpenRouter.
|
|
314
|
+
|
|
315
|
+
Returns ``None`` when real-LLM mode isn't available (caller should
|
|
316
|
+
fall back to synthetic). Otherwise returns a dict with per-forecaster
|
|
317
|
+
posteriors + per-forecaster RunCost.
|
|
318
|
+
|
|
319
|
+
Schema::
|
|
320
|
+
|
|
321
|
+
{
|
|
322
|
+
"hypermind_pred": [float, ...],
|
|
323
|
+
"hypermind_role_posts": [{role: prob}, ...],
|
|
324
|
+
"single_llm_pred": [float, ...],
|
|
325
|
+
"naive_ensemble_pred":[float, ...],
|
|
326
|
+
"costs": {
|
|
327
|
+
"hypermind": RunCost(...),
|
|
328
|
+
"single_llm": RunCost(...),
|
|
329
|
+
"naive_ensemble": RunCost(...),
|
|
330
|
+
},
|
|
331
|
+
}
|
|
332
|
+
"""
|
|
333
|
+
if not is_real_llm_available():
|
|
334
|
+
return None
|
|
335
|
+
client = make_real_client()
|
|
336
|
+
if client is None:
|
|
337
|
+
return None
|
|
338
|
+
|
|
339
|
+
hm_pred: list[float] = []
|
|
340
|
+
hm_roles: list[dict[str, float]] = []
|
|
341
|
+
sl_pred: list[float] = []
|
|
342
|
+
ne_pred: list[float] = []
|
|
343
|
+
cost_hm = RunCost()
|
|
344
|
+
cost_sl = RunCost()
|
|
345
|
+
cost_ne = RunCost()
|
|
346
|
+
|
|
347
|
+
# Sequential per-question (parallel within HyperMind / Naive ensemble).
|
|
348
|
+
for q, _a in questions:
|
|
349
|
+
hp, roles = await real_hypermind_posterior(client, model, q, cost_hm)
|
|
350
|
+
hm_pred.append(hp)
|
|
351
|
+
hm_roles.append(roles)
|
|
352
|
+
sl_pred.append(await real_single_llm_posterior(client, model, q, cost_sl))
|
|
353
|
+
ne_pred.append(await real_naive_ensemble_posterior(client, model, q, cost_ne))
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
"hypermind_pred": hm_pred,
|
|
357
|
+
"hypermind_role_posts": hm_roles,
|
|
358
|
+
"single_llm_pred": sl_pred,
|
|
359
|
+
"naive_ensemble_pred": ne_pred,
|
|
360
|
+
"costs": {
|
|
361
|
+
"hypermind": asdict(cost_hm),
|
|
362
|
+
"single_llm": asdict(cost_sl),
|
|
363
|
+
"naive_ensemble": asdict(cost_ne),
|
|
364
|
+
},
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
__all__ = [
|
|
369
|
+
"RunCost",
|
|
370
|
+
"is_real_llm_available",
|
|
371
|
+
"make_real_client",
|
|
372
|
+
"run_real_llm_forecasters",
|
|
373
|
+
]
|