algo-cli-runtime 0.14.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.
- algo_cli/__init__.py +3 -0
- algo_cli/__main__.py +7 -0
- algo_cli/_internal/__init__.py +12 -0
- algo_cli/_internal/policy_chain.py +259 -0
- algo_cli/action_registry.py +1047 -0
- algo_cli/agent_blocks.py +550 -0
- algo_cli/agent_pipeline.py +1457 -0
- algo_cli/agent_threads.py +308 -0
- algo_cli/animations.py +316 -0
- algo_cli/cache_admission.py +209 -0
- algo_cli/capability_mask.py +66 -0
- algo_cli/chat_protocol.py +116 -0
- algo_cli/chatgpt_auth.py +510 -0
- algo_cli/chatgpt_client.py +657 -0
- algo_cli/code_rag.py +479 -0
- algo_cli/config.py +651 -0
- algo_cli/context_budget.py +679 -0
- algo_cli/credential_helpers.py +315 -0
- algo_cli/deliberation.py +29 -0
- algo_cli/display.py +1470 -0
- algo_cli/evals/__init__.py +21 -0
- algo_cli/evals/algorithm_effectiveness.py +560 -0
- algo_cli/evals/competitive_harness_rating.py +702 -0
- algo_cli/evals/cot_quality.py +220 -0
- algo_cli/evals/harness_retrieval_benchmark.py +401 -0
- algo_cli/evals/performance_regression.py +136 -0
- algo_cli/evals/scorecard_grading.py +308 -0
- algo_cli/evals/session_distribution.py +84 -0
- algo_cli/execution_guardrails.py +806 -0
- algo_cli/extensions_manifest.py +84 -0
- algo_cli/git_evidence.py +227 -0
- algo_cli/google_workspace.py +407 -0
- algo_cli/google_workspace_auth.py +523 -0
- algo_cli/harness.py +2587 -0
- algo_cli/identity.py +557 -0
- algo_cli/index_compute_lab.py +228 -0
- algo_cli/inference_harness.py +70 -0
- algo_cli/intelligence/__init__.py +1103 -0
- algo_cli/intelligence/acrobat_config.py +307 -0
- algo_cli/intelligence/acrobat_manifests.py +338 -0
- algo_cli/intelligence/acrobat_models.py +195 -0
- algo_cli/intelligence/acrobat_pipeline.py +295 -0
- algo_cli/intelligence/acrobat_runtime.py +302 -0
- algo_cli/intelligence/acrobat_security.py +261 -0
- algo_cli/intelligence/acrobat_workflows.py +226 -0
- algo_cli/intelligence/actionability.py +165 -0
- algo_cli/intelligence/adversarial_audit.py +136 -0
- algo_cli/intelligence/agent_arena.py +92 -0
- algo_cli/intelligence/agent_benchmark.py +236 -0
- algo_cli/intelligence/agent_runtime.py +171 -0
- algo_cli/intelligence/agents_as_tools.py +70 -0
- algo_cli/intelligence/artifact_binding.py +80 -0
- algo_cli/intelligence/autonomous_engineer.py +1976 -0
- algo_cli/intelligence/backpressure.py +99 -0
- algo_cli/intelligence/bloom_filter.py +186 -0
- algo_cli/intelligence/bonferroni.py +66 -0
- algo_cli/intelligence/boundary_compaction.py +98 -0
- algo_cli/intelligence/catalog_verifier.py +172 -0
- algo_cli/intelligence/cavecrew.py +118 -0
- algo_cli/intelligence/changelog.py +176 -0
- algo_cli/intelligence/checkpoint_resume.py +92 -0
- algo_cli/intelligence/circuit_breaker.py +88 -0
- algo_cli/intelligence/clarification_gate.py +101 -0
- algo_cli/intelligence/code_graph.py +180 -0
- algo_cli/intelligence/coderank.py +97 -0
- algo_cli/intelligence/consistent_hash.py +150 -0
- algo_cli/intelligence/consortium_synthesis.py +139 -0
- algo_cli/intelligence/construction/__init__.py +241 -0
- algo_cli/intelligence/construction/common.py +273 -0
- algo_cli/intelligence/construction/documents.py +496 -0
- algo_cli/intelligence/construction/labor_units.py +1395 -0
- algo_cli/intelligence/construction/payments.py +470 -0
- algo_cli/intelligence/construction/risk.py +784 -0
- algo_cli/intelligence/content_extractor.py +132 -0
- algo_cli/intelligence/context_adaptive.py +102 -0
- algo_cli/intelligence/context_ops.py +95 -0
- algo_cli/intelligence/count_min.py +145 -0
- algo_cli/intelligence/cow_state.py +103 -0
- algo_cli/intelligence/critic_loop.py +119 -0
- algo_cli/intelligence/cross_source.py +113 -0
- algo_cli/intelligence/daemon_mode.py +99 -0
- algo_cli/intelligence/dag_orchestration.py +151 -0
- algo_cli/intelligence/deep_research.py +155 -0
- algo_cli/intelligence/degenerate_detector.py +78 -0
- algo_cli/intelligence/delta_report.py +92 -0
- algo_cli/intelligence/discovery_event_log.py +92 -0
- algo_cli/intelligence/document_ingest.py +298 -0
- algo_cli/intelligence/dual_layer_validate.py +151 -0
- algo_cli/intelligence/echo_fidelity.py +73 -0
- algo_cli/intelligence/ema_tuning.py +104 -0
- algo_cli/intelligence/event_log.py +92 -0
- algo_cli/intelligence/evidence_graph.py +114 -0
- algo_cli/intelligence/extension_host.py +162 -0
- algo_cli/intelligence/extension_manifest.py +115 -0
- algo_cli/intelligence/falsification_suite.py +178 -0
- algo_cli/intelligence/finance/__init__.py +169 -0
- algo_cli/intelligence/finance/anomalies.py +135 -0
- algo_cli/intelligence/finance/ap_ar.py +351 -0
- algo_cli/intelligence/finance/cash.py +162 -0
- algo_cli/intelligence/finance/close.py +332 -0
- algo_cli/intelligence/finance/common.py +244 -0
- algo_cli/intelligence/finance/construction.py +135 -0
- algo_cli/intelligence/finance/controls.py +172 -0
- algo_cli/intelligence/finance/evidence.py +119 -0
- algo_cli/intelligence/finance/exceptions.py +157 -0
- algo_cli/intelligence/finance/reconciliations.py +254 -0
- algo_cli/intelligence/finance/revenue.py +109 -0
- algo_cli/intelligence/finance/tax.py +74 -0
- algo_cli/intelligence/finance/workpapers.py +111 -0
- algo_cli/intelligence/finding_record.py +120 -0
- algo_cli/intelligence/flow_dag.py +267 -0
- algo_cli/intelligence/gatherer.py +223 -0
- algo_cli/intelligence/golden_master.py +98 -0
- algo_cli/intelligence/graph_rag.py +195 -0
- algo_cli/intelligence/group_chat.py +143 -0
- algo_cli/intelligence/hash_dedup.py +145 -0
- algo_cli/intelligence/hyperloglog.py +128 -0
- algo_cli/intelligence/incremental_index.py +316 -0
- algo_cli/intelligence/index_store.py +16 -0
- algo_cli/intelligence/iteration_plan.py +133 -0
- algo_cli/intelligence/kernel_plugins.py +167 -0
- algo_cli/intelligence/lesson_catalog.py +135 -0
- algo_cli/intelligence/llm_fallback.py +169 -0
- algo_cli/intelligence/log2_histogram.py +267 -0
- algo_cli/intelligence/lsp_integration.py +147 -0
- algo_cli/intelligence/memory_evolution.py +117 -0
- algo_cli/intelligence/minhash_lsh.py +182 -0
- algo_cli/intelligence/multi_model_score.py +174 -0
- algo_cli/intelligence/multi_tier_grade.py +211 -0
- algo_cli/intelligence/negative_controls.py +113 -0
- algo_cli/intelligence/numeric_clamp.py +63 -0
- algo_cli/intelligence/occ_editor.py +66 -0
- algo_cli/intelligence/output_normalize.py +112 -0
- algo_cli/intelligence/parallel_delegation.py +98 -0
- algo_cli/intelligence/parallel_fanout.py +104 -0
- algo_cli/intelligence/permission_modes.py +105 -0
- algo_cli/intelligence/pre_push_gate.py +68 -0
- algo_cli/intelligence/prefetch.py +171 -0
- algo_cli/intelligence/process_framework.py +217 -0
- algo_cli/intelligence/project_graph.py +387 -0
- algo_cli/intelligence/query_expansion.py +146 -0
- algo_cli/intelligence/ralph_loop.py +117 -0
- algo_cli/intelligence/rate_limiter.py +153 -0
- algo_cli/intelligence/refactor_transaction.py +94 -0
- algo_cli/intelligence/research_workspace.py +108 -0
- algo_cli/intelligence/retraction_ledger.py +72 -0
- algo_cli/intelligence/saga_pattern.py +88 -0
- algo_cli/intelligence/session_fork.py +100 -0
- algo_cli/intelligence/shadow_editor.py +67 -0
- algo_cli/intelligence/shell_session.py +213 -0
- algo_cli/intelligence/source_registry.py +143 -0
- algo_cli/intelligence/spawn_scales.py +99 -0
- algo_cli/intelligence/stat_stability.py +104 -0
- algo_cli/intelligence/structural_validator.py +148 -0
- algo_cli/intelligence/subagent_spawner.py +111 -0
- algo_cli/intelligence/symmetric_verify.py +70 -0
- algo_cli/intelligence/task_classifier.py +129 -0
- algo_cli/intelligence/team_execution.py +122 -0
- algo_cli/intelligence/tiered_access.py +121 -0
- algo_cli/intelligence/utility_registry.py +159 -0
- algo_cli/intuition_engine.py +560 -0
- algo_cli/intuition_injector.py +82 -0
- algo_cli/kernels/__init__.py +5 -0
- algo_cli/kernels/manifest.py +763 -0
- algo_cli/main.py +3903 -0
- algo_cli/memory_candidates.py +541 -0
- algo_cli/memory_echo_veil.py +394 -0
- algo_cli/memory_runtime.py +112 -0
- algo_cli/model_info.py +548 -0
- algo_cli/model_profile.py +160 -0
- algo_cli/model_routing.py +74 -0
- algo_cli/oneshot.py +331 -0
- algo_cli/perf_telemetry.py +389 -0
- algo_cli/plugins.py +245 -0
- algo_cli/private_event_store.py +654 -0
- algo_cli/quantization/__init__.py +24 -0
- algo_cli/quantization/lloyd_max.py +98 -0
- algo_cli/quantization/turbo_quant.py +308 -0
- algo_cli/reasoning/__init__.py +46 -0
- algo_cli/reasoning/combinatorial.py +356 -0
- algo_cli/reasoning/graph_of_thought.py +297 -0
- algo_cli/reasoning/mcts.py +220 -0
- algo_cli/reasoning/neuro_symbolic.py +250 -0
- algo_cli/reasoning/react.py +246 -0
- algo_cli/reasoning/reflexion.py +225 -0
- algo_cli/reasoning/tree_of_thought.py +241 -0
- algo_cli/reasoning_bridge.py +150 -0
- algo_cli/reconciliation.py +284 -0
- algo_cli/reflex.py +385 -0
- algo_cli/resources/docs/ALGO.md +13958 -0
- algo_cli/resources/docs/algo-cli-algorithm-evidence-contract.md +60 -0
- algo_cli/resources/docs/algo-cli-execution-verification-contract.md +59 -0
- algo_cli/resources/docs/algo-cli-memory-lifecycle-contract.md +72 -0
- algo_cli/resources/docs/harness-extension-cleanup-recommendation.md +41 -0
- algo_cli/resources/docs/index-compute-lab-integration.md +32 -0
- algo_cli/resources/docs/inference-harness-loop-blueprint-2026-06.md +55 -0
- algo_cli/resources/docs/main-split-map.md +35 -0
- algo_cli/resources/docs/privacy-and-context.md +48 -0
- algo_cli/resources/docs/reflex-loop-v0.2.md +354 -0
- algo_cli/resources/skills/README.md +26 -0
- algo_cli/resources/skills/algo-cli.md +59 -0
- algo_cli/resources/skills/edit-file-precision.md +49 -0
- algo_cli/resources/skills/harness-search-first.md +47 -0
- algo_cli/resources/skills/memory-recall-ritual.md +51 -0
- algo_cli/resources/skills/qol-algorithms.md +224 -0
- algo_cli/resources/skills/smart-error-recovery.md +56 -0
- algo_cli/resources/skills/tool-selection-cheatsheet.md +65 -0
- algo_cli/retrieval_algorithms.py +127 -0
- algo_cli/runtime_qos.py +236 -0
- algo_cli/runtime_services.py +320 -0
- algo_cli/session_commands.py +95 -0
- algo_cli/session_mode.py +113 -0
- algo_cli/skills.py +430 -0
- algo_cli/slash_dispatch.py +1265 -0
- algo_cli/small_context.py +206 -0
- algo_cli/spawn_budget.py +89 -0
- algo_cli/task_ledger.py +84 -0
- algo_cli/task_router.py +197 -0
- algo_cli/tool_context.py +94 -0
- algo_cli/tool_contract.py +99 -0
- algo_cli/tool_policy.py +357 -0
- algo_cli/tool_runtime.py +647 -0
- algo_cli/tools.py +3056 -0
- algo_cli/url_scheme.py +174 -0
- algo_cli/verify.py +154 -0
- algo_cli/version_manifest.py +178 -0
- algo_cli/vision_screenshot_verify.py +76 -0
- algo_cli/workspace_resolver.py +68 -0
- algo_cli/x_account.py +209 -0
- algo_cli/xai_auth.py +374 -0
- algo_cli/xai_client.py +600 -0
- algo_cli_runtime-0.14.0.dist-info/METADATA +369 -0
- algo_cli_runtime-0.14.0.dist-info/RECORD +237 -0
- algo_cli_runtime-0.14.0.dist-info/WHEEL +4 -0
- algo_cli_runtime-0.14.0.dist-info/entry_points.txt +3 -0
- algo_cli_runtime-0.14.0.dist-info/licenses/LICENSE +21 -0
- ollama_cli/__init__.py +67 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""Monte Carlo Tree Search (MCTS) for Reasoning.
|
|
2
|
+
|
|
3
|
+
Implements UCT (Upper Confidence bounds applied to Trees) for reasoning:
|
|
4
|
+
- Selection: Pick the most promising node using UCB1
|
|
5
|
+
- Expansion: Generate child thoughts from the selected node
|
|
6
|
+
- Simulation: Roll out to estimate value (lightweight or via LLM)
|
|
7
|
+
- Backpropagation: Update statistics up the tree
|
|
8
|
+
|
|
9
|
+
Effective for deep reasoning tasks where exploration-exploitation
|
|
10
|
+
tradeoffs matter. Integrates with ToT for structured tree search.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import math
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from ..chat_protocol import get_attr
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class MCTSNode:
|
|
25
|
+
"""A node in the MCTS search tree."""
|
|
26
|
+
thought: str
|
|
27
|
+
state: str # Accumulated reasoning state
|
|
28
|
+
parent: "MCTSNode | None" = field(default=None, repr=False)
|
|
29
|
+
children: list["MCTSNode"] = field(default_factory=list)
|
|
30
|
+
visits: int = 0
|
|
31
|
+
total_value: float = 0.0
|
|
32
|
+
depth: int = 0
|
|
33
|
+
fully_expanded: bool = False
|
|
34
|
+
terminal: bool = False
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def avg_value(self) -> float:
|
|
38
|
+
return self.total_value / max(1, self.visits)
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def ucb1(self) -> float:
|
|
42
|
+
"""UCT score with exploration constant sqrt(2)."""
|
|
43
|
+
if self.visits == 0:
|
|
44
|
+
return float("inf")
|
|
45
|
+
exploitation = self.avg_value
|
|
46
|
+
exploration = math.sqrt(2.0 * math.log(max(1, self.parent.visits if self.parent else 1)) / self.visits)
|
|
47
|
+
return exploitation + exploration
|
|
48
|
+
|
|
49
|
+
def best_child(self) -> "MCTSNode | None":
|
|
50
|
+
"""Select the child with the highest UCB1 score."""
|
|
51
|
+
if not self.children:
|
|
52
|
+
return None
|
|
53
|
+
return max(self.children, key=lambda c: c.ucb1)
|
|
54
|
+
|
|
55
|
+
def best_leaf(self) -> "MCTSNode":
|
|
56
|
+
"""Find the most-visited leaf (most robust conclusion)."""
|
|
57
|
+
if not self.children:
|
|
58
|
+
return self
|
|
59
|
+
return max(self.children, key=lambda c: c.visits).best_leaf()
|
|
60
|
+
|
|
61
|
+
def reasoning_chain(self) -> str:
|
|
62
|
+
"""Human-readable chain from root to this node."""
|
|
63
|
+
chain = []
|
|
64
|
+
node: MCTSNode | None = self
|
|
65
|
+
while node is not None:
|
|
66
|
+
chain.append(node)
|
|
67
|
+
node = node.parent
|
|
68
|
+
chain.reverse()
|
|
69
|
+
return "\n".join(
|
|
70
|
+
f"Step {i+1} (visits={n.visits}, val={n.avg_value:.2f}): {n.thought}"
|
|
71
|
+
for i, n in enumerate(chain)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class MCTSReasoner:
|
|
77
|
+
"""MCTS-based reasoning engine."""
|
|
78
|
+
max_iterations: int = 50
|
|
79
|
+
max_depth: int = 6
|
|
80
|
+
branch_factor: int = 3
|
|
81
|
+
rollout_depth: int = 2
|
|
82
|
+
exploration_constant: float = math.sqrt(2.0)
|
|
83
|
+
|
|
84
|
+
root: MCTSNode | None = None
|
|
85
|
+
nodes_created: int = 0
|
|
86
|
+
|
|
87
|
+
def _select(self) -> MCTSNode:
|
|
88
|
+
"""Selection: traverse tree following UCB1 to find a node to expand."""
|
|
89
|
+
node = self.root
|
|
90
|
+
while node and node.children and not node.terminal:
|
|
91
|
+
if not node.fully_expanded:
|
|
92
|
+
return node
|
|
93
|
+
selected = node.best_child()
|
|
94
|
+
if selected is None:
|
|
95
|
+
return node
|
|
96
|
+
node = selected
|
|
97
|
+
return node
|
|
98
|
+
|
|
99
|
+
def _expand(self, node: MCTSNode, client: Any, model: str) -> MCTSNode | None:
|
|
100
|
+
"""Expansion: generate a new child thought from the selected node."""
|
|
101
|
+
prompt = (
|
|
102
|
+
f"Current reasoning state:\n{node.state[:1500]}\n\n"
|
|
103
|
+
f"Generate one next reasoning step (different from existing children if any).\n"
|
|
104
|
+
f"Existing steps: {[c.thought[:80] for c in node.children]}\n"
|
|
105
|
+
f"Respond with just the reasoning step and updated state as JSON:\n"
|
|
106
|
+
f'{{"thought": "...", "state": "..."}}'
|
|
107
|
+
)
|
|
108
|
+
try:
|
|
109
|
+
response = client.chat(
|
|
110
|
+
model=model,
|
|
111
|
+
messages=[
|
|
112
|
+
{"role": "system", "content": "You are a reasoning step generator."},
|
|
113
|
+
{"role": "user", "content": prompt},
|
|
114
|
+
],
|
|
115
|
+
stream=False,
|
|
116
|
+
format="json",
|
|
117
|
+
)
|
|
118
|
+
text = get_attr(get_attr(response, "message", {}), "content", "")
|
|
119
|
+
data = json.loads(text)
|
|
120
|
+
thought = str(data.get("thought", ""))
|
|
121
|
+
state = str(data.get("state", node.state))
|
|
122
|
+
except (json.JSONDecodeError, ValueError, TypeError):
|
|
123
|
+
thought = f"(expansion at depth {node.depth + 1})"
|
|
124
|
+
state = node.state
|
|
125
|
+
|
|
126
|
+
child = MCTSNode(
|
|
127
|
+
thought=thought, state=state, parent=node,
|
|
128
|
+
depth=node.depth + 1,
|
|
129
|
+
)
|
|
130
|
+
node.children.append(child)
|
|
131
|
+
self.nodes_created += 1
|
|
132
|
+
|
|
133
|
+
if len(node.children) >= self.branch_factor:
|
|
134
|
+
node.fully_expanded = True
|
|
135
|
+
|
|
136
|
+
if node.depth + 1 >= self.max_depth:
|
|
137
|
+
child.terminal = True
|
|
138
|
+
|
|
139
|
+
return child
|
|
140
|
+
|
|
141
|
+
def _simulate(self, node: MCTSNode, client: Any, model: str) -> float:
|
|
142
|
+
"""Simulation: quick rollout to estimate the node's value."""
|
|
143
|
+
prompt = (
|
|
144
|
+
f"Given this reasoning chain, rate the quality of the conclusion (0.0-1.0):\n"
|
|
145
|
+
f"{node.state[:1000]}\n\nRespond with just a number."
|
|
146
|
+
)
|
|
147
|
+
try:
|
|
148
|
+
response = client.chat(
|
|
149
|
+
model=model,
|
|
150
|
+
messages=[
|
|
151
|
+
{"role": "system", "content": "You are a reasoning quality evaluator."},
|
|
152
|
+
{"role": "user", "content": prompt},
|
|
153
|
+
],
|
|
154
|
+
stream=False,
|
|
155
|
+
)
|
|
156
|
+
text = get_attr(get_attr(response, "message", {}), "content", "").strip()
|
|
157
|
+
import re
|
|
158
|
+
m = re.search(r"([0-9]*\.?[0-9]+)", text)
|
|
159
|
+
if m:
|
|
160
|
+
return max(0.0, min(1.0, float(m.group(1))))
|
|
161
|
+
except Exception:
|
|
162
|
+
pass
|
|
163
|
+
return 0.5
|
|
164
|
+
|
|
165
|
+
def _backpropagate(self, node: MCTSNode, value: float) -> None:
|
|
166
|
+
"""Backpropagation: update statistics from leaf to root."""
|
|
167
|
+
current: MCTSNode | None = node
|
|
168
|
+
while current is not None:
|
|
169
|
+
current.visits += 1
|
|
170
|
+
current.total_value += value
|
|
171
|
+
current = current.parent
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def run_mcts(
|
|
175
|
+
*,
|
|
176
|
+
task: str,
|
|
177
|
+
client: Any,
|
|
178
|
+
model: str,
|
|
179
|
+
max_iterations: int = 50,
|
|
180
|
+
max_depth: int = 6,
|
|
181
|
+
branch_factor: int = 3,
|
|
182
|
+
) -> MCTSReasoner:
|
|
183
|
+
"""Run MCTS reasoning on a task.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
task: The reasoning task.
|
|
187
|
+
client: Ollama client.
|
|
188
|
+
model: Model name for evaluation and generation.
|
|
189
|
+
max_iterations: Total MCTS iterations (select + expand + simulate + backprop).
|
|
190
|
+
max_depth: Maximum tree depth.
|
|
191
|
+
branch_factor: Max children per node.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
MCTSReasoner with root tree and best_leaf set.
|
|
195
|
+
"""
|
|
196
|
+
reasoner = MCTSReasoner(
|
|
197
|
+
max_iterations=max_iterations,
|
|
198
|
+
max_depth=max_depth,
|
|
199
|
+
branch_factor=branch_factor,
|
|
200
|
+
)
|
|
201
|
+
reasoner.root = MCTSNode(thought="(root)", state=task, visits=1)
|
|
202
|
+
|
|
203
|
+
for _ in range(max_iterations):
|
|
204
|
+
# 1. Select
|
|
205
|
+
node = reasoner._select()
|
|
206
|
+
if node is None or node.terminal:
|
|
207
|
+
break
|
|
208
|
+
|
|
209
|
+
# 2. Expand
|
|
210
|
+
child = reasoner._expand(node, client, model)
|
|
211
|
+
if child is None:
|
|
212
|
+
break
|
|
213
|
+
|
|
214
|
+
# 3. Simulate
|
|
215
|
+
value = reasoner._simulate(child, client, model)
|
|
216
|
+
|
|
217
|
+
# 4. Backpropagate
|
|
218
|
+
reasoner._backpropagate(child, value)
|
|
219
|
+
|
|
220
|
+
return reasoner
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""Neuro-Symbolic Verifier Loop.
|
|
2
|
+
|
|
3
|
+
Implements a guess-and-check pattern:
|
|
4
|
+
1. LLM proposes a solution (guess)
|
|
5
|
+
2. Symbolic/structured verifier checks correctness (check)
|
|
6
|
+
3. If verification fails, feed back the error and retry
|
|
7
|
+
|
|
8
|
+
Verifiers include:
|
|
9
|
+
- Python AST/expression evaluation for math/logic
|
|
10
|
+
- JSON schema validation for structured outputs
|
|
11
|
+
- Regex/grammar checks for format compliance
|
|
12
|
+
- Unit-test-like assertions for code
|
|
13
|
+
|
|
14
|
+
This dramatically reduces hallucination for formal domains.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import ast
|
|
20
|
+
import json
|
|
21
|
+
import re
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from typing import Any, Callable
|
|
24
|
+
|
|
25
|
+
from ..chat_protocol import get_attr
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class VerificationResult:
|
|
30
|
+
"""Result of verifying an LLM output."""
|
|
31
|
+
passed: bool
|
|
32
|
+
checks_total: int
|
|
33
|
+
checks_passed: int
|
|
34
|
+
failures: list[str] = field(default_factory=list)
|
|
35
|
+
suggestions: list[str] = field(default_factory=list)
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def score(self) -> float:
|
|
39
|
+
if self.checks_total == 0:
|
|
40
|
+
return 0.0
|
|
41
|
+
return self.checks_passed / self.checks_total
|
|
42
|
+
|
|
43
|
+
def feedback_message(self) -> str:
|
|
44
|
+
if self.passed:
|
|
45
|
+
return "All verification checks passed."
|
|
46
|
+
lines = [f"Verification failed: {self.checks_passed}/{self.checks_total} checks passed."]
|
|
47
|
+
for fail in self.failures:
|
|
48
|
+
lines.append(f" - {fail}")
|
|
49
|
+
if self.suggestions:
|
|
50
|
+
lines.append("Suggestions:")
|
|
51
|
+
for s in self.suggestions:
|
|
52
|
+
lines.append(f" + {s}")
|
|
53
|
+
return "\n".join(lines)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class NeuroSymbolicVerifier:
|
|
58
|
+
"""LLM propose + symbolic verify loop."""
|
|
59
|
+
max_rounds: int = 3
|
|
60
|
+
verifiers: list[Callable[[str], VerificationResult]] = field(default_factory=list)
|
|
61
|
+
|
|
62
|
+
def verify(self, output: str) -> VerificationResult:
|
|
63
|
+
"""Run all registered verifiers against the LLM output."""
|
|
64
|
+
if not self.verifiers:
|
|
65
|
+
return VerificationResult(passed=True, checks_total=0, checks_passed=0)
|
|
66
|
+
total = 0
|
|
67
|
+
passed = 0
|
|
68
|
+
failures: list[str] = []
|
|
69
|
+
suggestions: list[str] = []
|
|
70
|
+
for verifier in self.verifiers:
|
|
71
|
+
result = verifier(output)
|
|
72
|
+
total += result.checks_total
|
|
73
|
+
passed += result.checks_passed
|
|
74
|
+
failures.extend(result.failures)
|
|
75
|
+
suggestions.extend(result.suggestions)
|
|
76
|
+
return VerificationResult(
|
|
77
|
+
passed=passed == total and total > 0,
|
|
78
|
+
checks_total=total,
|
|
79
|
+
checks_passed=passed,
|
|
80
|
+
failures=failures,
|
|
81
|
+
suggestions=suggestions,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def add_verifier(self, verifier: Callable[[str], VerificationResult]) -> "NeuroSymbolicVerifier":
|
|
85
|
+
self.verifiers.append(verifier)
|
|
86
|
+
return self
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# --- Built-in verifiers ---
|
|
90
|
+
|
|
91
|
+
def json_schema_verifier(schema: dict[str, Any]) -> Callable[[str], VerificationResult]:
|
|
92
|
+
"""Create a verifier that checks if output is valid JSON matching a schema."""
|
|
93
|
+
def verify(output: str) -> VerificationResult:
|
|
94
|
+
failures: list[str] = []
|
|
95
|
+
try:
|
|
96
|
+
data = json.loads(output)
|
|
97
|
+
except json.JSONDecodeError as e:
|
|
98
|
+
return VerificationResult(
|
|
99
|
+
passed=False, checks_total=1, checks_passed=0,
|
|
100
|
+
failures=[f"Invalid JSON: {e}"],
|
|
101
|
+
suggestions=["Ensure output is valid JSON."],
|
|
102
|
+
)
|
|
103
|
+
# Check required keys
|
|
104
|
+
required = schema.get("required", [])
|
|
105
|
+
for key in required:
|
|
106
|
+
if key not in data:
|
|
107
|
+
failures.append(f"Missing required key: {key}")
|
|
108
|
+
# Check types
|
|
109
|
+
properties = schema.get("properties", {})
|
|
110
|
+
for key, expected_type in properties.items():
|
|
111
|
+
if key in data:
|
|
112
|
+
actual_type = type(data[key]).__name__
|
|
113
|
+
if expected_type != actual_type:
|
|
114
|
+
failures.append(f"Key '{key}' has type {actual_type}, expected {expected_type}")
|
|
115
|
+
passed = len(failures) == 0
|
|
116
|
+
return VerificationResult(
|
|
117
|
+
passed=passed,
|
|
118
|
+
checks_total=len(required) + len(properties),
|
|
119
|
+
checks_passed=len(required) + len(properties) - len(failures),
|
|
120
|
+
failures=failures,
|
|
121
|
+
suggestions=["Fix the schema violations listed above."],
|
|
122
|
+
)
|
|
123
|
+
return verify
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def python_syntax_verifier(output: str) -> VerificationResult:
|
|
127
|
+
"""Verify that Python code in the output is syntactically valid."""
|
|
128
|
+
# Extract code blocks
|
|
129
|
+
code_blocks = re.findall(r"```python\n(.*?)```", output, re.DOTALL)
|
|
130
|
+
if not code_blocks:
|
|
131
|
+
# Check if entire output looks like code
|
|
132
|
+
if any(kw in output for kw in ["def ", "class ", "import "]):
|
|
133
|
+
code_blocks = [output]
|
|
134
|
+
else:
|
|
135
|
+
return VerificationResult(passed=True, checks_total=0, checks_passed=0)
|
|
136
|
+
|
|
137
|
+
failures: list[str] = []
|
|
138
|
+
for i, code in enumerate(code_blocks):
|
|
139
|
+
try:
|
|
140
|
+
ast.parse(code)
|
|
141
|
+
except SyntaxError as e:
|
|
142
|
+
failures.append(f"Code block {i+1}: SyntaxError at line {e.lineno}: {e.msg}")
|
|
143
|
+
|
|
144
|
+
return VerificationResult(
|
|
145
|
+
passed=len(failures) == 0,
|
|
146
|
+
checks_total=len(code_blocks),
|
|
147
|
+
checks_passed=len(code_blocks) - len(failures),
|
|
148
|
+
failures=failures,
|
|
149
|
+
suggestions=["Fix syntax errors in the code blocks."],
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def regex_format_verifier(pattern: str, description: str = "") -> Callable[[str], VerificationResult]:
|
|
154
|
+
"""Create a verifier that checks if output matches a regex pattern."""
|
|
155
|
+
desc = description or f"matches pattern: {pattern[:50]}"
|
|
156
|
+
def verify(output: str) -> VerificationResult:
|
|
157
|
+
if re.search(pattern, output):
|
|
158
|
+
return VerificationResult(passed=True, checks_total=1, checks_passed=1)
|
|
159
|
+
return VerificationResult(
|
|
160
|
+
passed=False, checks_total=1, checks_passed=0,
|
|
161
|
+
failures=[f"Output does not {desc}"],
|
|
162
|
+
suggestions=[f"Ensure the output {desc}"],
|
|
163
|
+
)
|
|
164
|
+
return verify
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def assertion_verifier(assertions: list[tuple[str, str]]) -> Callable[[str], VerificationResult]:
|
|
168
|
+
"""Create a verifier from a list of (pattern, description) assertions.
|
|
169
|
+
|
|
170
|
+
Each assertion is a (regex_pattern, description) tuple.
|
|
171
|
+
The output must match ALL patterns.
|
|
172
|
+
"""
|
|
173
|
+
def verify(output: str) -> VerificationResult:
|
|
174
|
+
failures: list[str] = []
|
|
175
|
+
for pattern, desc in assertions:
|
|
176
|
+
if not re.search(pattern, output):
|
|
177
|
+
failures.append(f"Assertion failed: {desc}")
|
|
178
|
+
return VerificationResult(
|
|
179
|
+
passed=len(failures) == 0,
|
|
180
|
+
checks_total=len(assertions),
|
|
181
|
+
checks_passed=len(assertions) - len(failures),
|
|
182
|
+
failures=failures,
|
|
183
|
+
)
|
|
184
|
+
return verify
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
VERIFY_PROMPT_TEMPLATE = """You previously produced this output:
|
|
188
|
+
|
|
189
|
+
{output}
|
|
190
|
+
|
|
191
|
+
Verification found these issues:
|
|
192
|
+
{feedback}
|
|
193
|
+
|
|
194
|
+
Please revise your output to address all verification failures.
|
|
195
|
+
Produce the corrected output only, without explaining the changes."""
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def run_neuro_symbolic(
|
|
199
|
+
*,
|
|
200
|
+
task: str,
|
|
201
|
+
client: Any,
|
|
202
|
+
model: str,
|
|
203
|
+
verifiers: list[Callable[[str], VerificationResult]],
|
|
204
|
+
max_rounds: int = 3,
|
|
205
|
+
system: str = "You are a precise reasoning agent. Produce output that passes all verification checks.",
|
|
206
|
+
) -> tuple[str, list[VerificationResult]]:
|
|
207
|
+
"""Run a neuro-symbolic guess-and-check loop.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
task: The task to solve.
|
|
211
|
+
client: Ollama client.
|
|
212
|
+
model: Model name.
|
|
213
|
+
verifiers: List of verification functions.
|
|
214
|
+
max_rounds: Maximum guess-and-check rounds.
|
|
215
|
+
system: System prompt.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
(final_output, verification_history)
|
|
219
|
+
"""
|
|
220
|
+
ns = NeuroSymbolicVerifier(max_rounds=max_rounds, verifiers=verifiers)
|
|
221
|
+
messages: list[dict[str, Any]] = [
|
|
222
|
+
{"role": "system", "content": system},
|
|
223
|
+
{"role": "user", "content": task},
|
|
224
|
+
]
|
|
225
|
+
history: list[VerificationResult] = []
|
|
226
|
+
output = ""
|
|
227
|
+
|
|
228
|
+
for round_num in range(max_rounds):
|
|
229
|
+
try:
|
|
230
|
+
response = client.chat(model=model, messages=messages, stream=False)
|
|
231
|
+
output = get_attr(get_attr(response, "message", {}), "content", "").strip()
|
|
232
|
+
except Exception as exc:
|
|
233
|
+
output = f"Error: {exc}"
|
|
234
|
+
|
|
235
|
+
# Verify
|
|
236
|
+
result = ns.verify(output)
|
|
237
|
+
history.append(result)
|
|
238
|
+
|
|
239
|
+
if result.passed:
|
|
240
|
+
break
|
|
241
|
+
|
|
242
|
+
# Feed back failures for retry
|
|
243
|
+
feedback = result.feedback_message()
|
|
244
|
+
messages.append({"role": "assistant", "content": output})
|
|
245
|
+
messages.append({
|
|
246
|
+
"role": "user",
|
|
247
|
+
"content": VERIFY_PROMPT_TEMPLATE.format(output=output[:2000], feedback=feedback),
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
return output, history
|