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.
Files changed (181) hide show
  1. hmctl/__init__.py +7 -0
  2. hmctl/cli.py +947 -0
  3. hypermind/__init__.py +208 -0
  4. hypermind/_deprecations.py +108 -0
  5. hypermind/agent.py +2896 -0
  6. hypermind/agent_card.py +127 -0
  7. hypermind/api/__init__.py +32 -0
  8. hypermind/api/app.py +686 -0
  9. hypermind/capability.py +923 -0
  10. hypermind/consult.py +493 -0
  11. hypermind/consult_bias.py +112 -0
  12. hypermind/contract_net.py +219 -0
  13. hypermind/crypto/__init__.py +21 -0
  14. hypermind/crypto/cose_encrypt.py +126 -0
  15. hypermind/crypto/dkg.py +246 -0
  16. hypermind/crypto/domain.py +59 -0
  17. hypermind/crypto/frost.py +771 -0
  18. hypermind/crypto/hashing.py +38 -0
  19. hypermind/crypto/hlc.py +163 -0
  20. hypermind/crypto/hpke.py +277 -0
  21. hypermind/crypto/kms.py +196 -0
  22. hypermind/crypto/kms_backends/__init__.py +56 -0
  23. hypermind/crypto/kms_backends/aws.py +128 -0
  24. hypermind/crypto/kms_backends/azure.py +114 -0
  25. hypermind/crypto/kms_backends/gcp.py +97 -0
  26. hypermind/crypto/kms_backends/vault.py +120 -0
  27. hypermind/crypto/namespace_root.py +204 -0
  28. hypermind/crypto/pq.py +152 -0
  29. hypermind/crypto/room_epoch.py +97 -0
  30. hypermind/crypto/signing.py +245 -0
  31. hypermind/deliberation.py +355 -0
  32. hypermind/did_web.py +256 -0
  33. hypermind/dispute.py +858 -0
  34. hypermind/errors.py +218 -0
  35. hypermind/eval/__init__.py +49 -0
  36. hypermind/eval/calibration.py +307 -0
  37. hypermind/eval/comparison.py +251 -0
  38. hypermind/eval/replay.py +202 -0
  39. hypermind/eval/swarm_iq.py +718 -0
  40. hypermind/knowledge/__init__.py +17 -0
  41. hypermind/knowledge/citation_graph.py +141 -0
  42. hypermind/knowledge/correlated_agreement.py +167 -0
  43. hypermind/knowledge/embedding_registry.py +115 -0
  44. hypermind/knowledge/kernel_store.py +197 -0
  45. hypermind/knowledge/rekor.py +173 -0
  46. hypermind/knowledge/reputation.py +364 -0
  47. hypermind/knowledge/swarm_memory.py +523 -0
  48. hypermind/knowledge/transparency.py +409 -0
  49. hypermind/mcp/__init__.py +60 -0
  50. hypermind/mcp/bridge.py +151 -0
  51. hypermind/mcp/client.py +142 -0
  52. hypermind/mcp/provenance.py +214 -0
  53. hypermind/mcp/relata_tools.py +152 -0
  54. hypermind/mcp/server.py +248 -0
  55. hypermind/mcp/transport.py +206 -0
  56. hypermind/mind/__init__.py +150 -0
  57. hypermind/mind/active.py +234 -0
  58. hypermind/mind/belief.py +369 -0
  59. hypermind/mind/deliberator.py +625 -0
  60. hypermind/mind/diversity.py +144 -0
  61. hypermind/mind/evolve.py +184 -0
  62. hypermind/mind/federation.py +154 -0
  63. hypermind/mind/goals.py +117 -0
  64. hypermind/mind/integration.py +190 -0
  65. hypermind/mind/mediator.py +74 -0
  66. hypermind/mind/memory_store.py +129 -0
  67. hypermind/mind/policy.py +307 -0
  68. hypermind/mind/records.py +260 -0
  69. hypermind/mind/roles.py +190 -0
  70. hypermind/mind/sybil_guard.py +41 -0
  71. hypermind/mind/world_model.py +166 -0
  72. hypermind/namespace.py +515 -0
  73. hypermind/namespace_policy.py +254 -0
  74. hypermind/observability/__init__.py +25 -0
  75. hypermind/observability/asgi.py +214 -0
  76. hypermind/observability/cost.py +277 -0
  77. hypermind/observability/otel_export.py +200 -0
  78. hypermind/observability/recorder.py +344 -0
  79. hypermind/observability/swarm_trace.py +438 -0
  80. hypermind/pin_policy.py +116 -0
  81. hypermind/py.typed +0 -0
  82. hypermind/responders/__init__.py +87 -0
  83. hypermind/responders/anthropic.py +159 -0
  84. hypermind/responders/base.py +162 -0
  85. hypermind/responders/openai.py +199 -0
  86. hypermind/responders/router.py +254 -0
  87. hypermind/responders/structured.py +287 -0
  88. hypermind/responders/tools.py +444 -0
  89. hypermind/revocation.py +270 -0
  90. hypermind/rule_ids.py +135 -0
  91. hypermind/schemas/encrypted_statement.cddl +28 -0
  92. hypermind/schemas/namespace_accept.cddl +20 -0
  93. hypermind/schemas/namespace_create.cddl +25 -0
  94. hypermind/schemas/namespace_founding_attest.cddl +30 -0
  95. hypermind/schemas/namespace_invite.cddl +22 -0
  96. hypermind/schemas/wire-v0.1.cddl +73 -0
  97. hypermind/simlab/__init__.py +15 -0
  98. hypermind/simlab/_persona_registry.py +93 -0
  99. hypermind/simlab/_scenario_impl.py +2778 -0
  100. hypermind/simlab/app.py +2538 -0
  101. hypermind/simlab/backends/__init__.py +27 -0
  102. hypermind/simlab/backends/anchor.py +88 -0
  103. hypermind/simlab/backends/local.py +32 -0
  104. hypermind/simlab/backends/server.py +79 -0
  105. hypermind/simlab/cli_command.py +108 -0
  106. hypermind/simlab/config.py +106 -0
  107. hypermind/simlab/deployment.py +26 -0
  108. hypermind/simlab/knowledge.py +716 -0
  109. hypermind/simlab/learning.py +295 -0
  110. hypermind/simlab/modes/__init__.py +115 -0
  111. hypermind/simlab/modes/_eval_real_llm.py +373 -0
  112. hypermind/simlab/modes/aar.py +611 -0
  113. hypermind/simlab/modes/backtest.py +610 -0
  114. hypermind/simlab/modes/binary_forecast.py +69 -0
  115. hypermind/simlab/modes/conformance.py +1869 -0
  116. hypermind/simlab/modes/evaluation.py +2271 -0
  117. hypermind/simlab/modes/governance.py +648 -0
  118. hypermind/simlab/modes/redteam.py +534 -0
  119. hypermind/simlab/modes/tabletop.py +797 -0
  120. hypermind/simlab/modes/tournament.py +448 -0
  121. hypermind/simlab/modes/whatif.py +523 -0
  122. hypermind/simlab/namespace.py +125 -0
  123. hypermind/simlab/personas.py +477 -0
  124. hypermind/simlab/prompt_generator.py +172 -0
  125. hypermind/simlab/question_sets/geopolitics_q20.json +122 -0
  126. hypermind/simlab/question_sets/governance_q5.json +67 -0
  127. hypermind/simlab/question_sets/msft_outlook_q10.json +62 -0
  128. hypermind/simlab/question_sets/whatif_q3.json +38 -0
  129. hypermind/simlab/registry.py +325 -0
  130. hypermind/simlab/runner.py +239 -0
  131. hypermind/simlab/scenario.py +147 -0
  132. hypermind/simlab/swarms.py +180 -0
  133. hypermind/simlab/tool_handlers/__init__.py +4 -0
  134. hypermind/simlab/tool_handlers/alpha_vantage.py +39 -0
  135. hypermind/simlab/tool_handlers/brave.py +46 -0
  136. hypermind/simlab/tool_handlers/newsapi.py +50 -0
  137. hypermind/simlab/tool_handlers/openweather.py +46 -0
  138. hypermind/simlab/tool_handlers/pinecone.py +52 -0
  139. hypermind/simlab/tool_handlers/qdrant.py +57 -0
  140. hypermind/simlab/tool_handlers/query_knowledge_base.py +21 -0
  141. hypermind/simlab/tool_handlers/relata_recall.py +61 -0
  142. hypermind/simlab/tool_handlers/retrieve_document.py +21 -0
  143. hypermind/simlab/tool_handlers/serper.py +46 -0
  144. hypermind/simlab/tool_handlers/tavily.py +53 -0
  145. hypermind/simlab/tool_handlers/weaviate.py +65 -0
  146. hypermind/simlab/tool_handlers/wikipedia.py +64 -0
  147. hypermind/simlab/tool_handlers/wolfram.py +48 -0
  148. hypermind/simlab/tool_handlers/yahoo_finance.py +49 -0
  149. hypermind/simlab/tool_registry.py +568 -0
  150. hypermind/simlab/topic_adapter.py +338 -0
  151. hypermind/simlab/topic_questions.py +259 -0
  152. hypermind/storage/__init__.py +69 -0
  153. hypermind/storage/backend.py +71 -0
  154. hypermind/storage/relata.py +245 -0
  155. hypermind/storage/sqlite.py +258 -0
  156. hypermind/sync.py +191 -0
  157. hypermind/tal.py +175 -0
  158. hypermind/tasks.py +334 -0
  159. hypermind/testing.py +16 -0
  160. hypermind/tools/__init__.py +32 -0
  161. hypermind/tools/registry.py +61 -0
  162. hypermind/transport/__init__.py +22 -0
  163. hypermind/transport/anti_entropy.py +708 -0
  164. hypermind/transport/bus.py +206 -0
  165. hypermind/transport/knows_delta.py +204 -0
  166. hypermind/transport/libp2p.py +298 -0
  167. hypermind/transport/placement.py +58 -0
  168. hypermind/transport/tcp.py +797 -0
  169. hypermind/transport/tls_profile.py +417 -0
  170. hypermind/types.py +59 -0
  171. hypermind/uncertainty.py +222 -0
  172. hypermind/wire.py +897 -0
  173. hypermind/workflow/__init__.py +72 -0
  174. hypermind/workflow/engine.py +655 -0
  175. hypermind/workflow/plan.py +182 -0
  176. hypermind/workflow/repair.py +203 -0
  177. hypermind-0.11.0.dist-info/METADATA +524 -0
  178. hypermind-0.11.0.dist-info/RECORD +181 -0
  179. hypermind-0.11.0.dist-info/WHEEL +4 -0
  180. hypermind-0.11.0.dist-info/entry_points.txt +2 -0
  181. 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
+ ]