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.
- crp/__init__.py +126 -0
- crp/__main__.py +8 -0
- crp/_typing.py +27 -0
- crp/_version.py +5 -0
- crp/adapters.py +31 -0
- crp/advanced/__init__.py +40 -0
- crp/advanced/auto_ingest.py +400 -0
- crp/advanced/cqs.py +235 -0
- crp/advanced/cross_window.py +477 -0
- crp/advanced/curator.py +265 -0
- crp/advanced/feedback.py +146 -0
- crp/advanced/hierarchical.py +211 -0
- crp/advanced/meta_learning.py +401 -0
- crp/advanced/parallel.py +98 -0
- crp/advanced/review_cycle.py +329 -0
- crp/advanced/scale_mode.py +129 -0
- crp/advanced/source_grounding.py +207 -0
- crp/ckf/__init__.py +35 -0
- crp/ckf/community.py +377 -0
- crp/ckf/fabric.py +445 -0
- crp/ckf/gc.py +175 -0
- crp/ckf/graph_walk.py +87 -0
- crp/ckf/merge.py +133 -0
- crp/ckf/pattern_query.py +122 -0
- crp/ckf/pubsub.py +128 -0
- crp/ckf/semantic.py +207 -0
- crp/cli/__init__.py +7 -0
- crp/cli/main.py +329 -0
- crp/cli/sidecar.py +929 -0
- crp/cli/startup.py +272 -0
- crp/continuation/__init__.py +103 -0
- crp/continuation/completion.py +348 -0
- crp/continuation/degradation.py +157 -0
- crp/continuation/document_map.py +160 -0
- crp/continuation/flow.py +109 -0
- crp/continuation/gap.py +419 -0
- crp/continuation/manager.py +484 -0
- crp/continuation/quality_monitor.py +179 -0
- crp/continuation/stitch.py +419 -0
- crp/continuation/trigger.py +142 -0
- crp/continuation/voice.py +157 -0
- crp/core/__init__.py +69 -0
- crp/core/batch.py +77 -0
- crp/core/circuit_breaker.py +116 -0
- crp/core/config.py +377 -0
- crp/core/context_tools.py +540 -0
- crp/core/dispatch_router.py +3977 -0
- crp/core/errors.py +128 -0
- crp/core/extraction_facade.py +384 -0
- crp/core/facilitator.py +713 -0
- crp/core/idempotency.py +215 -0
- crp/core/orchestrator.py +1435 -0
- crp/core/relay_strategies.py +613 -0
- crp/core/security_manager.py +140 -0
- crp/core/session.py +134 -0
- crp/core/task_intent.py +36 -0
- crp/core/window.py +363 -0
- crp/envelope/__init__.py +30 -0
- crp/envelope/builder.py +288 -0
- crp/envelope/decomposer.py +236 -0
- crp/envelope/formatter.py +168 -0
- crp/envelope/packer.py +211 -0
- crp/envelope/reranker.py +209 -0
- crp/envelope/scoring.py +310 -0
- crp/extraction/__init__.py +45 -0
- crp/extraction/complexity.py +96 -0
- crp/extraction/contradiction.py +132 -0
- crp/extraction/pipeline.py +360 -0
- crp/extraction/quality_gate.py +237 -0
- crp/extraction/stage1_regex.py +173 -0
- crp/extraction/stage2_statistical.py +244 -0
- crp/extraction/stage3_gliner.py +210 -0
- crp/extraction/stage4_uie.py +183 -0
- crp/extraction/stage5_discourse.py +175 -0
- crp/extraction/stage6_llm.py +178 -0
- crp/extraction/structured_output.py +219 -0
- crp/extraction/types.py +299 -0
- crp/license_guard.py +722 -0
- crp/observability/__init__.py +30 -0
- crp/observability/audit.py +118 -0
- crp/observability/events.py +233 -0
- crp/observability/metrics.py +264 -0
- crp/observability/quality.py +135 -0
- crp/observability/structured_logging.py +81 -0
- crp/observability/telemetry.py +117 -0
- crp/provenance/__init__.py +314 -0
- crp/provenance/_embeddings.py +97 -0
- crp/provenance/_types.py +378 -0
- crp/provenance/attribution_scorer.py +252 -0
- crp/provenance/claim_detector.py +229 -0
- crp/provenance/contradiction_detector.py +243 -0
- crp/provenance/distortion_detector.py +397 -0
- crp/provenance/entailment_verifier.py +358 -0
- crp/provenance/fabrication_detector.py +203 -0
- crp/provenance/hallucination_scorer.py +320 -0
- crp/provenance/omission_analyzer.py +106 -0
- crp/provenance/provenance_chain.py +205 -0
- crp/provenance/report_generator.py +440 -0
- crp/providers/__init__.py +43 -0
- crp/providers/anthropic.py +270 -0
- crp/providers/base.py +135 -0
- crp/providers/custom.py +63 -0
- crp/providers/diagnostic.py +251 -0
- crp/providers/llamacpp.py +224 -0
- crp/providers/manager.py +139 -0
- crp/providers/ollama.py +243 -0
- crp/providers/openai.py +628 -0
- crp/providers/tokenizers.py +48 -0
- crp/py.typed +0 -0
- crp/resources/__init__.py +53 -0
- crp/resources/adaptive_allocator.py +525 -0
- crp/resources/cost_model.py +388 -0
- crp/resources/overhead_manager.py +217 -0
- crp/resources/resource_manager.py +262 -0
- crp/schemas/__init__.py +20 -0
- crp/schemas/cost-estimate.json +33 -0
- crp/schemas/crp-error.json +43 -0
- crp/schemas/envelope-preview.json +40 -0
- crp/schemas/persisted-state-header.json +27 -0
- crp/schemas/quality-report.json +94 -0
- crp/schemas/session-handle.json +33 -0
- crp/schemas/session-status.json +57 -0
- crp/schemas/stream-event.json +18 -0
- crp/schemas/task-intent.json +42 -0
- crp/security/__init__.py +93 -0
- crp/security/audit_trail.py +392 -0
- crp/security/binding.py +192 -0
- crp/security/compliance.py +813 -0
- crp/security/consent.py +593 -0
- crp/security/embedding_defense.py +161 -0
- crp/security/encryption.py +202 -0
- crp/security/injection.py +335 -0
- crp/security/integrity.py +267 -0
- crp/security/privacy.py +662 -0
- crp/security/quarantine.py +249 -0
- crp/security/rbac.py +221 -0
- crp/security/validation.py +164 -0
- crp/state/__init__.py +31 -0
- crp/state/cold_storage.py +258 -0
- crp/state/compaction.py +263 -0
- crp/state/critical_state.py +104 -0
- crp/state/event_log.py +313 -0
- crp/state/fact.py +189 -0
- crp/state/serialization.py +189 -0
- crp/state/session_cleanup.py +77 -0
- crp/state/snapshot.py +290 -0
- crp/state/warm_store.py +346 -0
- crprotocol-2.0.0.dist-info/METADATA +1295 -0
- crprotocol-2.0.0.dist-info/RECORD +153 -0
- crprotocol-2.0.0.dist-info/WHEEL +4 -0
- crprotocol-2.0.0.dist-info/entry_points.txt +2 -0
- crprotocol-2.0.0.dist-info/licenses/LICENSE.md +170 -0
- crprotocol-2.0.0.dist-info/licenses/NOTICE +18 -0
crp/providers/ollama.py
ADDED
|
@@ -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
|