crprotocol 2.0.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 (153) hide show
  1. crp/__init__.py +126 -0
  2. crp/__main__.py +8 -0
  3. crp/_typing.py +27 -0
  4. crp/_version.py +5 -0
  5. crp/adapters.py +31 -0
  6. crp/advanced/__init__.py +40 -0
  7. crp/advanced/auto_ingest.py +400 -0
  8. crp/advanced/cqs.py +235 -0
  9. crp/advanced/cross_window.py +477 -0
  10. crp/advanced/curator.py +265 -0
  11. crp/advanced/feedback.py +146 -0
  12. crp/advanced/hierarchical.py +211 -0
  13. crp/advanced/meta_learning.py +401 -0
  14. crp/advanced/parallel.py +98 -0
  15. crp/advanced/review_cycle.py +329 -0
  16. crp/advanced/scale_mode.py +129 -0
  17. crp/advanced/source_grounding.py +207 -0
  18. crp/ckf/__init__.py +35 -0
  19. crp/ckf/community.py +377 -0
  20. crp/ckf/fabric.py +445 -0
  21. crp/ckf/gc.py +175 -0
  22. crp/ckf/graph_walk.py +87 -0
  23. crp/ckf/merge.py +133 -0
  24. crp/ckf/pattern_query.py +122 -0
  25. crp/ckf/pubsub.py +128 -0
  26. crp/ckf/semantic.py +207 -0
  27. crp/cli/__init__.py +7 -0
  28. crp/cli/main.py +329 -0
  29. crp/cli/sidecar.py +929 -0
  30. crp/cli/startup.py +272 -0
  31. crp/continuation/__init__.py +103 -0
  32. crp/continuation/completion.py +348 -0
  33. crp/continuation/degradation.py +157 -0
  34. crp/continuation/document_map.py +160 -0
  35. crp/continuation/flow.py +109 -0
  36. crp/continuation/gap.py +419 -0
  37. crp/continuation/manager.py +484 -0
  38. crp/continuation/quality_monitor.py +179 -0
  39. crp/continuation/stitch.py +419 -0
  40. crp/continuation/trigger.py +142 -0
  41. crp/continuation/voice.py +157 -0
  42. crp/core/__init__.py +69 -0
  43. crp/core/batch.py +77 -0
  44. crp/core/circuit_breaker.py +116 -0
  45. crp/core/config.py +377 -0
  46. crp/core/context_tools.py +540 -0
  47. crp/core/dispatch_router.py +3977 -0
  48. crp/core/errors.py +128 -0
  49. crp/core/extraction_facade.py +384 -0
  50. crp/core/facilitator.py +713 -0
  51. crp/core/idempotency.py +215 -0
  52. crp/core/orchestrator.py +1435 -0
  53. crp/core/relay_strategies.py +613 -0
  54. crp/core/security_manager.py +140 -0
  55. crp/core/session.py +134 -0
  56. crp/core/task_intent.py +36 -0
  57. crp/core/window.py +363 -0
  58. crp/envelope/__init__.py +30 -0
  59. crp/envelope/builder.py +288 -0
  60. crp/envelope/decomposer.py +236 -0
  61. crp/envelope/formatter.py +168 -0
  62. crp/envelope/packer.py +211 -0
  63. crp/envelope/reranker.py +209 -0
  64. crp/envelope/scoring.py +310 -0
  65. crp/extraction/__init__.py +45 -0
  66. crp/extraction/complexity.py +96 -0
  67. crp/extraction/contradiction.py +132 -0
  68. crp/extraction/pipeline.py +360 -0
  69. crp/extraction/quality_gate.py +237 -0
  70. crp/extraction/stage1_regex.py +173 -0
  71. crp/extraction/stage2_statistical.py +244 -0
  72. crp/extraction/stage3_gliner.py +210 -0
  73. crp/extraction/stage4_uie.py +183 -0
  74. crp/extraction/stage5_discourse.py +175 -0
  75. crp/extraction/stage6_llm.py +178 -0
  76. crp/extraction/structured_output.py +219 -0
  77. crp/extraction/types.py +299 -0
  78. crp/license_guard.py +722 -0
  79. crp/observability/__init__.py +30 -0
  80. crp/observability/audit.py +118 -0
  81. crp/observability/events.py +233 -0
  82. crp/observability/metrics.py +264 -0
  83. crp/observability/quality.py +135 -0
  84. crp/observability/structured_logging.py +81 -0
  85. crp/observability/telemetry.py +117 -0
  86. crp/provenance/__init__.py +314 -0
  87. crp/provenance/_embeddings.py +97 -0
  88. crp/provenance/_types.py +378 -0
  89. crp/provenance/attribution_scorer.py +252 -0
  90. crp/provenance/claim_detector.py +229 -0
  91. crp/provenance/contradiction_detector.py +243 -0
  92. crp/provenance/distortion_detector.py +397 -0
  93. crp/provenance/entailment_verifier.py +358 -0
  94. crp/provenance/fabrication_detector.py +203 -0
  95. crp/provenance/hallucination_scorer.py +320 -0
  96. crp/provenance/omission_analyzer.py +106 -0
  97. crp/provenance/provenance_chain.py +205 -0
  98. crp/provenance/report_generator.py +440 -0
  99. crp/providers/__init__.py +43 -0
  100. crp/providers/anthropic.py +270 -0
  101. crp/providers/base.py +135 -0
  102. crp/providers/custom.py +63 -0
  103. crp/providers/diagnostic.py +251 -0
  104. crp/providers/llamacpp.py +224 -0
  105. crp/providers/manager.py +139 -0
  106. crp/providers/ollama.py +243 -0
  107. crp/providers/openai.py +628 -0
  108. crp/providers/tokenizers.py +48 -0
  109. crp/py.typed +0 -0
  110. crp/resources/__init__.py +53 -0
  111. crp/resources/adaptive_allocator.py +525 -0
  112. crp/resources/cost_model.py +388 -0
  113. crp/resources/overhead_manager.py +217 -0
  114. crp/resources/resource_manager.py +262 -0
  115. crp/schemas/__init__.py +20 -0
  116. crp/schemas/cost-estimate.json +33 -0
  117. crp/schemas/crp-error.json +43 -0
  118. crp/schemas/envelope-preview.json +40 -0
  119. crp/schemas/persisted-state-header.json +27 -0
  120. crp/schemas/quality-report.json +94 -0
  121. crp/schemas/session-handle.json +33 -0
  122. crp/schemas/session-status.json +57 -0
  123. crp/schemas/stream-event.json +18 -0
  124. crp/schemas/task-intent.json +42 -0
  125. crp/security/__init__.py +93 -0
  126. crp/security/audit_trail.py +392 -0
  127. crp/security/binding.py +192 -0
  128. crp/security/compliance.py +813 -0
  129. crp/security/consent.py +593 -0
  130. crp/security/embedding_defense.py +161 -0
  131. crp/security/encryption.py +202 -0
  132. crp/security/injection.py +335 -0
  133. crp/security/integrity.py +267 -0
  134. crp/security/privacy.py +662 -0
  135. crp/security/quarantine.py +249 -0
  136. crp/security/rbac.py +221 -0
  137. crp/security/validation.py +164 -0
  138. crp/state/__init__.py +31 -0
  139. crp/state/cold_storage.py +258 -0
  140. crp/state/compaction.py +263 -0
  141. crp/state/critical_state.py +104 -0
  142. crp/state/event_log.py +313 -0
  143. crp/state/fact.py +189 -0
  144. crp/state/serialization.py +189 -0
  145. crp/state/session_cleanup.py +77 -0
  146. crp/state/snapshot.py +290 -0
  147. crp/state/warm_store.py +346 -0
  148. crprotocol-2.0.0.dist-info/METADATA +1295 -0
  149. crprotocol-2.0.0.dist-info/RECORD +153 -0
  150. crprotocol-2.0.0.dist-info/WHEEL +4 -0
  151. crprotocol-2.0.0.dist-info/entry_points.txt +2 -0
  152. crprotocol-2.0.0.dist-info/licenses/LICENSE.md +170 -0
  153. crprotocol-2.0.0.dist-info/licenses/NOTICE +18 -0
@@ -0,0 +1,243 @@
1
+ # Copyright © 2025 Constantinos Vidiniotis. All rights reserved.
2
+ # Licensed under Elastic License 2.0 — see LICENSE.md for details.
3
+ """Ollama adapter — local model inference via Ollama REST API (§6.1).
4
+
5
+ Requires a running Ollama instance (``ollama serve``).
6
+
7
+ Usage::
8
+
9
+ from crp.providers.ollama import OllamaAdapter
10
+
11
+ provider = OllamaAdapter(model="llama3.1")
12
+ output, reason = provider.generate_chat([
13
+ {"role": "system", "content": "You are helpful."},
14
+ {"role": "user", "content": "Hello!"},
15
+ ])
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import logging
22
+ import os
23
+ import random
24
+ import time
25
+ import urllib.request
26
+ import urllib.error
27
+ from typing import Any
28
+
29
+ from crp.providers.base import LLMProvider
30
+
31
+ logger = logging.getLogger("crp.providers.ollama")
32
+
33
+ # Model family → context window (tokens).
34
+ # Ollama defaults to 2048; most models support much more with num_ctx.
35
+ _MODEL_CONTEXT: dict[str, int] = {
36
+ "llama3.1": 128_000,
37
+ "llama3.2": 128_000,
38
+ "llama3.3": 128_000,
39
+ "llama3": 8_192,
40
+ "llama2": 4_096,
41
+ "mistral": 32_768,
42
+ "mixtral": 32_768,
43
+ "gemma2": 8_192,
44
+ "gemma3": 128_000,
45
+ "qwen3": 40_960,
46
+ "qwen2.5": 128_000,
47
+ "qwen2": 128_000,
48
+ "qwen": 32_768,
49
+ "phi3": 128_000,
50
+ "phi4": 16_384,
51
+ "deepseek-r1": 128_000,
52
+ "deepseek-v3": 128_000,
53
+ "deepseek-coder": 128_000,
54
+ "command-r": 128_000,
55
+ "codellama": 16_384,
56
+ "yi": 200_000,
57
+ "internlm": 256_000,
58
+ "rwkv": 100_000,
59
+ "youtu": 128_000,
60
+ }
61
+
62
+
63
+ def _resolve_context(model: str, base_url: str | None = None) -> int:
64
+ """Resolve context window for a model.
65
+
66
+ Strategy (3-layer):
67
+ 1. Prefix match against known families
68
+ 2. Server probe via /api/show (Ollama exposes model metadata)
69
+ 3. Conservative fallback (4_096) — safe for unknown models
70
+ """
71
+ # Layer 1: prefix match
72
+ for prefix, ctx in _MODEL_CONTEXT.items():
73
+ if model.startswith(prefix):
74
+ return ctx
75
+
76
+ # Layer 2: Ollama /api/show probe
77
+ if base_url:
78
+ try:
79
+ import json
80
+ req = urllib.request.Request(
81
+ f"{base_url.rstrip('/')}/api/show",
82
+ data=json.dumps({"name": model}).encode("utf-8"),
83
+ headers={"Content-Type": "application/json"},
84
+ method="POST",
85
+ )
86
+ with urllib.request.urlopen(req, timeout=5) as resp:
87
+ data = json.loads(resp.read())
88
+ params = data.get("model_info", {})
89
+ ctx = params.get("context_length") or params.get("num_ctx")
90
+ if ctx and isinstance(ctx, int) and ctx > 0:
91
+ logger.info("Ollama probe: model '%s' → ctx=%d", model, ctx)
92
+ return ctx
93
+ except Exception:
94
+ pass
95
+
96
+ logger.warning(
97
+ "Model '%s' not in any known table. Using conservative default (ctx=4096). "
98
+ "Override with context_size= parameter.",
99
+ model,
100
+ )
101
+ return 4_096 # Conservative default
102
+
103
+
104
+ class OllamaAdapter(LLMProvider):
105
+ """Ollama REST API adapter for local model inference.
106
+
107
+ Args:
108
+ model: Model name (e.g. "llama3.1", "mistral", "gemma2").
109
+ base_url: Ollama API base URL. Defaults to ``OLLAMA_HOST``
110
+ env var or ``http://localhost:11434``.
111
+ context_size: Override context window size (tokens).
112
+ max_tokens: Max output tokens per request (default: 2048).
113
+ timeout: HTTP timeout in seconds (default: 300 — local
114
+ models can be slow on CPU).
115
+ """
116
+
117
+ def __init__(
118
+ self,
119
+ model: str = "llama3.1",
120
+ *,
121
+ base_url: str | None = None,
122
+ context_size: int | None = None,
123
+ max_tokens: int = 2_048,
124
+ timeout: float = 300.0,
125
+ ) -> None:
126
+ self._model = model
127
+ self._base_url = (
128
+ base_url
129
+ or os.environ.get("OLLAMA_HOST")
130
+ or "http://localhost:11434"
131
+ ).rstrip("/")
132
+ self._context_size = context_size or _resolve_context(model, self._base_url)
133
+ self._max_tokens = max_tokens
134
+ self._timeout = timeout
135
+
136
+ # Connection pooling: reuse a persistent opener with keep-alive
137
+ self._opener = urllib.request.build_opener(
138
+ urllib.request.HTTPHandler(),
139
+ urllib.request.HTTPSHandler(),
140
+ )
141
+
142
+ logger.debug(
143
+ "OllamaAdapter initialized: model=%s, url=%s, ctx=%d",
144
+ model, self._base_url, self._context_size,
145
+ )
146
+
147
+ # -- LLMProvider interface --------------------------------------------
148
+
149
+ # Retry config: 4 attempts with exponential backoff + jitter
150
+ _MAX_RETRIES = 4
151
+ _BASE_DELAY = 2.0
152
+
153
+ @staticmethod
154
+ def _is_retryable(exc: Exception) -> bool:
155
+ """Check if an exception is transient and worth retrying."""
156
+ if isinstance(exc, (ConnectionError, TimeoutError, OSError)):
157
+ return True
158
+ if isinstance(exc, urllib.error.URLError):
159
+ return True
160
+ exc_msg = str(exc).lower()
161
+ return any(kw in exc_msg for kw in ("connection", "reset", "refused", "timeout"))
162
+
163
+ def generate_chat(
164
+ self, messages: list[dict[str, str]], **kwargs: Any
165
+ ) -> tuple[str, str]:
166
+ """Call Ollama /api/chat endpoint with retry on transient failures.
167
+
168
+ Returns (output_text, finish_reason).
169
+ """
170
+ payload = {
171
+ "model": self._model,
172
+ "messages": messages,
173
+ "stream": False,
174
+ "options": {
175
+ "num_predict": kwargs.pop("max_tokens", self._max_tokens),
176
+ "num_ctx": self._context_size,
177
+ },
178
+ }
179
+
180
+ url = f"{self._base_url}/api/chat"
181
+ data = json.dumps(payload).encode("utf-8")
182
+
183
+ last_exc: Exception | None = None
184
+ for attempt in range(self._MAX_RETRIES):
185
+ req = urllib.request.Request(
186
+ url,
187
+ data=data,
188
+ headers={
189
+ "Content-Type": "application/json",
190
+ "Connection": "keep-alive",
191
+ },
192
+ method="POST",
193
+ )
194
+
195
+ try:
196
+ with self._opener.open(req, timeout=self._timeout) as resp:
197
+ body = json.loads(resp.read())
198
+
199
+ text = body.get("message", {}).get("content", "")
200
+ done = body.get("done", True)
201
+ done_reason = body.get("done_reason", "stop")
202
+
203
+ if done_reason == "length" or not done:
204
+ finish_reason = "length"
205
+ else:
206
+ finish_reason = "stop"
207
+
208
+ return (text, finish_reason)
209
+
210
+ except Exception as exc:
211
+ last_exc = exc
212
+ if attempt < self._MAX_RETRIES - 1 and self._is_retryable(exc):
213
+ delay = self._BASE_DELAY * (2 ** attempt) + random.uniform(0, 0.5)
214
+ logger.warning(
215
+ "Ollama request failed (attempt %d/%d), retrying in %.1fs: %s",
216
+ attempt + 1, self._MAX_RETRIES, delay, exc,
217
+ )
218
+ time.sleep(delay)
219
+ continue
220
+ logger.error("Ollama API error (non-retryable): %s", exc)
221
+ return ("", "error")
222
+
223
+ logger.error("Ollama API failed after %d retries: %s", self._MAX_RETRIES, last_exc)
224
+ return ("", "error")
225
+
226
+ def count_tokens(self, text: str) -> int:
227
+ """Estimate token count (Ollama doesn't expose tokenizer directly).
228
+
229
+ Uses a conservative ~3.5 chars/token estimate for most models.
230
+ For exact counts, use the LlamaCppAdapter with model_path instead.
231
+ """
232
+ return max(1, len(text.encode("utf-8")) * 10 // 35)
233
+
234
+ def context_window_size(self) -> int:
235
+ return self._context_size
236
+
237
+ @property
238
+ def max_output_tokens(self) -> int | None:
239
+ return self._max_tokens
240
+
241
+ @property
242
+ def model_name(self) -> str:
243
+ return self._model