orizu 0.5.20 → 0.5.21
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.
- package/dist/archive-artifacts-cli.d.ts +50 -0
- package/dist/archive-artifacts-cli.d.ts.map +1 -0
- package/dist/artifact-archive.d.ts +48 -0
- package/dist/artifact-archive.d.ts.map +1 -0
- package/dist/artifact-pull.d.ts +22 -0
- package/dist/artifact-pull.d.ts.map +1 -0
- package/dist/banner.d.ts +3 -0
- package/dist/banner.d.ts.map +1 -0
- package/dist/connectors-cli.d.ts +31 -0
- package/dist/connectors-cli.d.ts.map +1 -0
- package/dist/credentials.d.ts +46 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/csv.d.ts +49 -0
- package/dist/csv.d.ts.map +1 -0
- package/dist/dataset-download.d.ts +6 -0
- package/dist/dataset-download.d.ts.map +1 -0
- package/dist/dataset-edit-rows.d.ts +10 -0
- package/dist/dataset-edit-rows.d.ts.map +1 -0
- package/dist/dataset-upload-snapshot.d.ts +13 -0
- package/dist/dataset-upload-snapshot.d.ts.map +1 -0
- package/dist/daytona-slice-rehearsal.d.ts +89 -0
- package/dist/daytona-slice-rehearsal.d.ts.map +1 -0
- package/dist/daytona-slice.d.ts +106 -0
- package/dist/daytona-slice.d.ts.map +1 -0
- package/dist/deploy-key-merge-core.d.ts +121 -0
- package/dist/deploy-key-merge-core.d.ts.map +1 -0
- package/dist/diff-comments-cli.d.ts +12 -0
- package/dist/diff-comments-cli.d.ts.map +1 -0
- package/dist/diff-comments-cli.js +3 -0
- package/dist/egress-allowlist-cli.d.ts +23 -0
- package/dist/egress-allowlist-cli.d.ts.map +1 -0
- package/dist/egress-policy.d.ts +108 -0
- package/dist/egress-policy.d.ts.map +1 -0
- package/dist/error-response.d.ts +9 -0
- package/dist/error-response.d.ts.map +1 -0
- package/dist/file-parser.d.ts +5 -0
- package/dist/file-parser.d.ts.map +1 -0
- package/dist/gepa-engine-dispatch.d.ts +9 -0
- package/dist/gepa-engine-dispatch.d.ts.map +1 -0
- package/dist/gepa-engine-dispatch.js +48 -2
- package/dist/gepa-python-command.d.ts +6 -0
- package/dist/gepa-python-command.d.ts.map +1 -0
- package/dist/gepa-python-paths.d.ts +6 -0
- package/dist/gepa-python-paths.d.ts.map +1 -0
- package/dist/gepa-python-paths.js +6 -3
- package/dist/git-credential.d.ts +69 -0
- package/dist/git-credential.d.ts.map +1 -0
- package/dist/git-recovery-environment.d.ts +31 -0
- package/dist/git-recovery-environment.d.ts.map +1 -0
- package/dist/github-setup.d.ts +122 -0
- package/dist/github-setup.d.ts.map +1 -0
- package/dist/global-flags.d.ts +12 -0
- package/dist/global-flags.d.ts.map +1 -0
- package/dist/help.d.ts +46 -0
- package/dist/help.d.ts.map +1 -0
- package/dist/help.js +91 -23
- package/dist/hosted-boot.d.ts +273 -0
- package/dist/hosted-boot.d.ts.map +1 -0
- package/dist/hosted-bootstrap-rehearsal.d.ts +86 -0
- package/dist/hosted-bootstrap-rehearsal.d.ts.map +1 -0
- package/dist/hosted-bootstrap.d.ts +188 -0
- package/dist/hosted-bootstrap.d.ts.map +1 -0
- package/dist/hosted-broker-server.d.ts +50 -0
- package/dist/hosted-broker-server.d.ts.map +1 -0
- package/dist/hosted-git-http-server.d.ts +36 -0
- package/dist/hosted-git-http-server.d.ts.map +1 -0
- package/dist/hosted-harness-claude.d.ts +195 -0
- package/dist/hosted-harness-claude.d.ts.map +1 -0
- package/dist/hosted-harness-opencode.d.ts +227 -0
- package/dist/hosted-harness-opencode.d.ts.map +1 -0
- package/dist/hosted-harness.d.ts +118 -0
- package/dist/hosted-harness.d.ts.map +1 -0
- package/dist/hosted-harvest.d.ts +68 -0
- package/dist/hosted-harvest.d.ts.map +1 -0
- package/dist/hosted-headless.d.ts +72 -0
- package/dist/hosted-headless.d.ts.map +1 -0
- package/dist/hosted-loop-lifecycle.d.ts +206 -0
- package/dist/hosted-loop-lifecycle.d.ts.map +1 -0
- package/dist/hosted-loop.d.ts +170 -0
- package/dist/hosted-loop.d.ts.map +1 -0
- package/dist/hosted-run-event-sink.d.ts +222 -0
- package/dist/hosted-run-event-sink.d.ts.map +1 -0
- package/dist/hosted-runtime-assets.d.ts +186 -0
- package/dist/hosted-runtime-assets.d.ts.map +1 -0
- package/dist/hosted-session-cli.d.ts +258 -0
- package/dist/hosted-session-cli.d.ts.map +1 -0
- package/dist/hosted-skill-staging.d.ts +73 -0
- package/dist/hosted-skill-staging.d.ts.map +1 -0
- package/dist/http.d.ts +8 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +88 -92
- package/dist/instruction-set-gepa-launch.d.ts +3 -0
- package/dist/instruction-set-gepa-launch.d.ts.map +1 -0
- package/dist/instruction-set-gepa-launch.js +15 -0
- package/dist/instruction-set-loader.d.ts +6 -0
- package/dist/instruction-set-loader.d.ts.map +1 -0
- package/dist/instruction-set-loader.js +59 -0
- package/dist/instruction-set-manifest.d.ts +14 -0
- package/dist/instruction-set-manifest.d.ts.map +1 -0
- package/dist/instruction-set-manifest.js +162 -0
- package/dist/instruction-sets-cli.d.ts +40 -0
- package/dist/instruction-sets-cli.d.ts.map +1 -0
- package/dist/instruction-sets-cli.js +361 -0
- package/dist/json-response.d.ts +4 -0
- package/dist/json-response.d.ts.map +1 -0
- package/dist/jsonl-stream.d.ts +8 -0
- package/dist/jsonl-stream.d.ts.map +1 -0
- package/dist/kill-switch-cli.d.ts +30 -0
- package/dist/kill-switch-cli.d.ts.map +1 -0
- package/dist/loopback-serve.d.ts +25 -0
- package/dist/loopback-serve.d.ts.map +1 -0
- package/dist/manifests-cli.d.ts +22 -0
- package/dist/manifests-cli.d.ts.map +1 -0
- package/dist/markdown-report-input.d.ts +7 -0
- package/dist/markdown-report-input.d.ts.map +1 -0
- package/dist/merge-job.d.ts +155 -0
- package/dist/merge-job.d.ts.map +1 -0
- package/dist/model-configs-cli.d.ts +19 -0
- package/dist/model-configs-cli.d.ts.map +1 -0
- package/dist/model-configs-cli.js +104 -0
- package/dist/optimization-export-cli.d.ts +19 -0
- package/dist/optimization-export-cli.d.ts.map +1 -0
- package/dist/optimizations-list-cli.d.ts +20 -0
- package/dist/optimizations-list-cli.d.ts.map +1 -0
- package/dist/optimizations-promote-cli.d.ts +8 -0
- package/dist/optimizations-promote-cli.d.ts.map +1 -0
- package/dist/optimizations-promote-cli.js +34 -0
- package/dist/option-validation.d.ts +15 -0
- package/dist/option-validation.d.ts.map +1 -0
- package/dist/preview-runtime.d.ts +63 -0
- package/dist/preview-runtime.d.ts.map +1 -0
- package/dist/prompt-draft-push.d.ts +60 -0
- package/dist/prompt-draft-push.d.ts.map +1 -0
- package/dist/prompt-label-cli.d.ts +11 -0
- package/dist/prompt-label-cli.d.ts.map +1 -0
- package/dist/prompt-label-cli.js +29 -0
- package/dist/prompt-length-wire.d.ts +28 -0
- package/dist/prompt-length-wire.d.ts.map +1 -0
- package/dist/prompt-report-cli.d.ts +10 -0
- package/dist/prompt-report-cli.d.ts.map +1 -0
- package/dist/prompt-summary-table.d.ts +15 -0
- package/dist/prompt-summary-table.d.ts.map +1 -0
- package/dist/prompt-summary-table.js +6 -0
- package/dist/registration-tag-warning.d.ts +32 -0
- package/dist/registration-tag-warning.d.ts.map +1 -0
- package/dist/report-comments-cli.d.ts +16 -0
- package/dist/report-comments-cli.d.ts.map +1 -0
- package/dist/report-markdown-blankness.d.ts +2 -0
- package/dist/report-markdown-blankness.d.ts.map +1 -0
- package/dist/report-replacement-warning.d.ts +17 -0
- package/dist/report-replacement-warning.d.ts.map +1 -0
- package/dist/repository-backup-core.d.ts +225 -0
- package/dist/repository-backup-core.d.ts.map +1 -0
- package/dist/repository-recovery-manifest.d.ts +83 -0
- package/dist/repository-recovery-manifest.d.ts.map +1 -0
- package/dist/run-event-digest.d.ts +74 -0
- package/dist/run-event-digest.d.ts.map +1 -0
- package/dist/runner-dir-verify.d.ts +66 -0
- package/dist/runner-dir-verify.d.ts.map +1 -0
- package/dist/runner-instruction-set-contract.d.ts +24 -0
- package/dist/runner-instruction-set-contract.d.ts.map +1 -0
- package/dist/runner-instruction-set-contract.js +50 -0
- package/dist/sandbox-hygiene.d.ts +45 -0
- package/dist/sandbox-hygiene.d.ts.map +1 -0
- package/dist/sandbox-provider.d.ts +193 -0
- package/dist/sandbox-provider.d.ts.map +1 -0
- package/dist/scorer-draft-push.d.ts +50 -0
- package/dist/scorer-draft-push.d.ts.map +1 -0
- package/dist/scorers-cli.d.ts +20 -0
- package/dist/scorers-cli.d.ts.map +1 -0
- package/dist/scorers-cli.js +22 -0
- package/dist/scorers-verify-parity-bridge.d.ts +16 -0
- package/dist/scorers-verify-parity-bridge.d.ts.map +1 -0
- package/dist/scorers-verify-parity-bridge.js +90 -0
- package/dist/scorers-verify-parity.d.ts +81 -0
- package/dist/scorers-verify-parity.d.ts.map +1 -0
- package/dist/scorers-verify-parity.js +502 -0
- package/dist/scores-accept-cli.d.ts +21 -0
- package/dist/scores-accept-cli.d.ts.map +1 -0
- package/dist/secret-redaction.d.ts +47 -0
- package/dist/secret-redaction.d.ts.map +1 -0
- package/dist/session-timeline.d.ts +54 -0
- package/dist/session-timeline.d.ts.map +1 -0
- package/dist/setup-prompt.d.ts +5 -0
- package/dist/setup-prompt.d.ts.map +1 -0
- package/dist/skill-installer.d.ts +68 -0
- package/dist/skill-installer.d.ts.map +1 -0
- package/dist/skilled-proposer-launch.d.ts +7 -0
- package/dist/skilled-proposer-launch.d.ts.map +1 -0
- package/dist/skilled-proposer-launch.js +60 -0
- package/dist/smoke.d.ts +102 -0
- package/dist/smoke.d.ts.map +1 -0
- package/dist/start-intent.d.ts +12 -0
- package/dist/start-intent.d.ts.map +1 -0
- package/dist/task-assignment-manifest.d.ts +15 -0
- package/dist/task-assignment-manifest.d.ts.map +1 -0
- package/dist/task-create-error.d.ts +18 -0
- package/dist/task-create-error.d.ts.map +1 -0
- package/dist/team-connectors-cli.d.ts +29 -0
- package/dist/team-connectors-cli.d.ts.map +1 -0
- package/dist/types.d.ts +39 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/vercel-sandbox-provider.d.ts +156 -0
- package/dist/vercel-sandbox-provider.d.ts.map +1 -0
- package/dist/workbench-cli.d.ts +233 -0
- package/dist/workbench-cli.d.ts.map +1 -0
- package/dist/workspace-sync.d.ts +207 -0
- package/dist/workspace-sync.d.ts.map +1 -0
- package/dist/workspace.d.ts +92 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/zip-draft-push.d.ts +55 -0
- package/dist/zip-draft-push.d.ts.map +1 -0
- package/package.json +14 -1
- package/requirements/README.md +138 -0
- package/requirements/skilled-proposer.in +66 -0
- package/requirements/skilled-proposer.lock +265 -0
- package/scripts/ensure-skilled-proposer-venv.mjs +5 -0
- package/scripts/refresh-skilled-proposer-lock.mjs +256 -0
- package/src/diff-comments-cli.ts +4 -0
- package/src/gepa-engine-dispatch.ts +42 -2
- package/src/gepa-python-paths.ts +6 -3
- package/src/help.ts +93 -23
- package/src/index.ts +99 -126
- package/src/instruction-set-gepa-launch.ts +28 -0
- package/src/instruction-set-loader.ts +37 -0
- package/src/instruction-set-manifest.ts +120 -0
- package/src/instruction-sets-cli.ts +306 -0
- package/src/model-configs-cli.ts +119 -0
- package/src/optimizations-promote-cli.ts +41 -0
- package/src/prompt-label-cli.ts +55 -0
- package/src/prompt-summary-table.ts +12 -0
- package/src/runner-instruction-set-contract.ts +64 -0
- package/src/scorers-cli.ts +40 -0
- package/src/scorers-verify-parity-bridge.ts +90 -0
- package/src/scorers-verify-parity.ts +564 -0
- package/src/skilled-proposer-launch.ts +89 -0
- package/src/skilled-proposer-venv-manager.mjs +757 -0
- package/tsconfig.json +2 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/__init__.py +2 -1
- package/vendor/orizu-gepa/src/orizu_gepa_connector/adapter.py +8 -2
- package/vendor/orizu-gepa/src/orizu_gepa_connector/callbacks.py +17 -3
- package/vendor/orizu-gepa/src/orizu_gepa_connector/engine.py +100 -8
- package/vendor/orizu-gepa/src/orizu_gepa_connector/instruction_set_loader.py +52 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/reflection.py +92 -12
- package/vendor/orizu-gepa/src/orizu_gepa_connector/runtime.py +100 -22
- package/vendor/orizu-gepa/src/orizu_gepa_connector/skilled_proposer_bridge.py +381 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/stop_conditions.py +17 -0
- package/vendor/orizu-gepa/src/orizu_gepa_connector/translator.py +2 -1
- package/vendor/orizu-gepa-python/src/orizu_gepa/client.py +52 -7
- package/vendor/orizu-gepa-python/src/orizu_gepa/local_log.py +4 -1
- package/vendor/orizu-gepa-python/src/orizu_gepa/optimizer.py +10 -5
- package/vendor/orizu-gepa-python/src/orizu_gepa/reflection.py +323 -68
- package/vendor/orizu-gepa-python/src/orizu_gepa/runner.py +207 -13
- package/vendor/skills/orizu-cli/SKILL.md +2 -0
- package/vendor/skills/orizu-cli/references/cli-reference.md +85 -1
- package/vendor/skills/orizu-cli/references/migrate-existing-gepa-setup.md +303 -0
- package/vendor/skills/orizu-cli/references/optimization-with-gepa.md +25 -0
- package/vendor/skills/orizu-cli/references/prompt-control-plane.md +108 -8
|
@@ -11,6 +11,7 @@ import time
|
|
|
11
11
|
import urllib.error
|
|
12
12
|
import urllib.request
|
|
13
13
|
from collections.abc import Callable
|
|
14
|
+
from dataclasses import dataclass
|
|
14
15
|
from typing import Any
|
|
15
16
|
|
|
16
17
|
from .optimizer import (
|
|
@@ -29,8 +30,62 @@ RETRY_BACKOFF_JITTER_SECONDS = 5.0
|
|
|
29
30
|
RETRYABLE_DIRECT_ERRORS = (TimeoutError, socket.timeout, ConnectionResetError, ConnectionAbortedError)
|
|
30
31
|
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class ProviderCompletion:
|
|
35
|
+
"""The provider facts needed by every reflection caller.
|
|
36
|
+
|
|
37
|
+
``usage`` is provider-derived: Anthropic provides input/output counts and
|
|
38
|
+
its total is their exact sum; OpenAI provides all three fields directly.
|
|
39
|
+
No caller is allowed to replace these values with a character estimate.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
text: str
|
|
43
|
+
usage: dict[str, int]
|
|
44
|
+
request_id: str | None
|
|
45
|
+
latency_ms: float
|
|
46
|
+
provider: str
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class _JsonHttpResponse:
|
|
51
|
+
data: dict[str, Any]
|
|
52
|
+
headers: dict[str, str]
|
|
53
|
+
latency_ms: float = 0.0
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ReflectionProviderError(RuntimeError):
|
|
57
|
+
"""A provider transport failure with a stable, operator-visible code."""
|
|
58
|
+
|
|
59
|
+
def __init__(self, code: str, message: str, *, usage: dict[str, int] | None = None) -> None:
|
|
60
|
+
super().__init__(f"{code}: {message}")
|
|
61
|
+
self.code = code
|
|
62
|
+
self.usage = usage
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class RetryableReflectionProviderError(RetryableReflectionError):
|
|
66
|
+
"""A retryable provider failure which preserves GEPA's existing type check."""
|
|
67
|
+
|
|
68
|
+
def __init__(self, code: str, message: str) -> None:
|
|
69
|
+
super().__init__(f"{code}: {message}")
|
|
70
|
+
self.code = code
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _headers_to_dict(headers: Any) -> dict[str, str]:
|
|
74
|
+
if headers is None:
|
|
75
|
+
return {}
|
|
76
|
+
return {str(name): str(value) for name, value in headers.items()}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _header_value(headers: dict[str, str], name: str) -> str | None:
|
|
80
|
+
for header_name, value in headers.items():
|
|
81
|
+
if header_name.lower() == name.lower():
|
|
82
|
+
return value
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _read_anthropic_response_with_curl(api_key: str, payload: dict[str, Any]) -> _JsonHttpResponse:
|
|
87
|
+
with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".json") as body_file, \
|
|
88
|
+
tempfile.NamedTemporaryFile("w+", encoding="utf-8", suffix=".headers") as headers_file:
|
|
34
89
|
json.dump(payload, body_file)
|
|
35
90
|
body_file.flush()
|
|
36
91
|
curl_config = "\n".join([
|
|
@@ -45,16 +100,30 @@ def _read_anthropic_response_with_curl(api_key: str, payload: dict[str, Any]) ->
|
|
|
45
100
|
"show-error",
|
|
46
101
|
])
|
|
47
102
|
result = subprocess.run(
|
|
48
|
-
["curl", "--config", "-"],
|
|
103
|
+
["curl", "--config", "-", "--dump-header", headers_file.name],
|
|
49
104
|
input=curl_config,
|
|
50
105
|
text=True,
|
|
51
106
|
capture_output=True,
|
|
52
107
|
check=False,
|
|
53
108
|
)
|
|
109
|
+
headers_file.seek(0)
|
|
110
|
+
header_lines = headers_file.read().splitlines()
|
|
54
111
|
if result.returncode != 0:
|
|
55
112
|
detail = "\n".join(part for part in [result.stderr.strip(), result.stdout.strip()] if part)
|
|
56
|
-
raise
|
|
57
|
-
|
|
113
|
+
raise ReflectionProviderError("ALI_1505_ANTHROPIC_CURL_FAILURE", f"Reflection LM failed via curl: {detail}")
|
|
114
|
+
try:
|
|
115
|
+
data = json.loads(result.stdout)
|
|
116
|
+
except json.JSONDecodeError as error:
|
|
117
|
+
raise ReflectionProviderError("ALI_1505_PROVIDER_RESPONSE_INVALID_JSON", "Reflection LM curl response was not JSON") from error
|
|
118
|
+
if not isinstance(data, dict):
|
|
119
|
+
raise ReflectionProviderError("ALI_1505_PROVIDER_RESPONSE_INVALID_SHAPE", "Reflection LM curl response must be a JSON object")
|
|
120
|
+
headers: dict[str, str] = {}
|
|
121
|
+
for line in header_lines[1:]:
|
|
122
|
+
if ":" not in line:
|
|
123
|
+
continue
|
|
124
|
+
name, value = line.split(":", 1)
|
|
125
|
+
headers[name.strip()] = value.strip()
|
|
126
|
+
return _JsonHttpResponse(data=data, headers=headers)
|
|
58
127
|
|
|
59
128
|
|
|
60
129
|
def _validate_positive_http_config(value: int, name: str) -> int:
|
|
@@ -81,47 +150,87 @@ def _read_json_response_with_retries(
|
|
|
81
150
|
timeout_seconds: int,
|
|
82
151
|
retry_attempts: int,
|
|
83
152
|
failure_prefix: str,
|
|
84
|
-
ssl_fallback: Callable[[],
|
|
85
|
-
|
|
153
|
+
ssl_fallback: Callable[[], _JsonHttpResponse] | None = None,
|
|
154
|
+
ssl_context: ssl.SSLContext | None = None,
|
|
155
|
+
) -> _JsonHttpResponse:
|
|
86
156
|
timeout_seconds = _validate_positive_http_config(timeout_seconds, "reflection_http_timeout_seconds")
|
|
87
157
|
retry_attempts = _validate_positive_http_config(retry_attempts, "reflection_retry_attempts")
|
|
88
158
|
|
|
89
159
|
for attempt in range(retry_attempts):
|
|
160
|
+
attempt_started = time.monotonic()
|
|
90
161
|
try:
|
|
91
|
-
|
|
92
|
-
|
|
162
|
+
urlopen_kwargs: dict[str, Any] = {"timeout": timeout_seconds}
|
|
163
|
+
if ssl_context is not None:
|
|
164
|
+
urlopen_kwargs["context"] = ssl_context
|
|
165
|
+
with urllib.request.urlopen(request, **urlopen_kwargs) as response:
|
|
166
|
+
try:
|
|
167
|
+
data = json.loads(response.read().decode("utf-8"))
|
|
168
|
+
except json.JSONDecodeError as error:
|
|
169
|
+
raise ReflectionProviderError(
|
|
170
|
+
"ALI_1505_PROVIDER_RESPONSE_INVALID_JSON",
|
|
171
|
+
f"{failure_prefix}: provider response was not JSON",
|
|
172
|
+
) from error
|
|
173
|
+
if not isinstance(data, dict):
|
|
174
|
+
raise ReflectionProviderError(
|
|
175
|
+
"ALI_1505_PROVIDER_RESPONSE_INVALID_SHAPE",
|
|
176
|
+
f"{failure_prefix}: provider response must be a JSON object",
|
|
177
|
+
)
|
|
178
|
+
return _JsonHttpResponse(
|
|
179
|
+
data=data,
|
|
180
|
+
headers=_headers_to_dict(getattr(response, "headers", None)),
|
|
181
|
+
latency_ms=(time.monotonic() - attempt_started) * 1000,
|
|
182
|
+
)
|
|
93
183
|
except urllib.error.HTTPError as error:
|
|
94
184
|
if _is_retryable_http_status(error.code) and attempt < retry_attempts - 1:
|
|
185
|
+
error.close()
|
|
95
186
|
time.sleep(_retry_backoff_seconds(attempt))
|
|
96
187
|
continue
|
|
97
|
-
|
|
188
|
+
try:
|
|
189
|
+
detail = error.read().decode("utf-8", errors="replace")
|
|
190
|
+
finally:
|
|
191
|
+
error.close()
|
|
98
192
|
if _is_retryable_http_status(error.code):
|
|
99
|
-
raise
|
|
193
|
+
raise RetryableReflectionProviderError(
|
|
194
|
+
"ALI_1505_PROVIDER_RETRYABLE_HTTP_FAILURE",
|
|
100
195
|
f"{failure_prefix} retryable HTTP failure after {retry_attempts} attempts: "
|
|
101
196
|
f"{error.code} {detail}"
|
|
102
197
|
) from error
|
|
103
|
-
raise
|
|
198
|
+
raise ReflectionProviderError(
|
|
199
|
+
"ALI_1505_PROVIDER_HTTP_FAILURE", f"{failure_prefix}: {error.code} {detail}"
|
|
200
|
+
) from error
|
|
104
201
|
except urllib.error.URLError as error:
|
|
105
202
|
if isinstance(error.reason, ssl.SSLCertVerificationError) and ssl_fallback is not None:
|
|
106
|
-
|
|
203
|
+
fallback_started = time.monotonic()
|
|
204
|
+
response = ssl_fallback()
|
|
205
|
+
return _JsonHttpResponse(
|
|
206
|
+
data=response.data,
|
|
207
|
+
headers=response.headers,
|
|
208
|
+
latency_ms=(time.monotonic() - fallback_started) * 1000,
|
|
209
|
+
)
|
|
107
210
|
if _is_retryable_url_error(error):
|
|
108
211
|
if attempt < retry_attempts - 1:
|
|
109
212
|
time.sleep(_retry_backoff_seconds(attempt))
|
|
110
213
|
continue
|
|
111
|
-
raise
|
|
214
|
+
raise RetryableReflectionProviderError(
|
|
215
|
+
"ALI_1505_PROVIDER_RETRYABLE_CONNECTION_FAILURE",
|
|
112
216
|
f"{failure_prefix} retryable connection failure after {retry_attempts} attempts: "
|
|
113
217
|
f"{error.reason}"
|
|
114
218
|
) from error
|
|
115
|
-
raise
|
|
219
|
+
raise ReflectionProviderError(
|
|
220
|
+
"ALI_1505_PROVIDER_CONNECTION_FAILURE", f"{failure_prefix}: {error.reason}"
|
|
221
|
+
) from error
|
|
116
222
|
except RETRYABLE_DIRECT_ERRORS as error:
|
|
117
223
|
if attempt < retry_attempts - 1:
|
|
118
224
|
time.sleep(_retry_backoff_seconds(attempt))
|
|
119
225
|
continue
|
|
120
|
-
raise
|
|
226
|
+
raise RetryableReflectionProviderError(
|
|
227
|
+
"ALI_1505_PROVIDER_TIMEOUT",
|
|
121
228
|
f"{failure_prefix} timed out after {retry_attempts} attempts: {error}"
|
|
122
229
|
) from error
|
|
123
230
|
|
|
124
|
-
raise
|
|
231
|
+
raise RetryableReflectionProviderError(
|
|
232
|
+
"ALI_1505_PROVIDER_RETRY_EXHAUSTED", f"{failure_prefix} failed after {retry_attempts} attempts"
|
|
233
|
+
)
|
|
125
234
|
|
|
126
235
|
|
|
127
236
|
def _merge_provider_settings(
|
|
@@ -158,12 +267,14 @@ def _require_anthropic_reflection_max_tokens(value: int | None) -> int:
|
|
|
158
267
|
return value
|
|
159
268
|
|
|
160
269
|
|
|
161
|
-
def
|
|
270
|
+
def _build_anthropic_completion_payload(
|
|
271
|
+
model: str, messages: list[dict[str, Any]], config: TextGepaConfig,
|
|
272
|
+
) -> dict[str, Any]:
|
|
162
273
|
max_tokens = _require_anthropic_reflection_max_tokens(config.reflection_max_tokens)
|
|
163
274
|
base_payload: dict[str, Any] = {
|
|
164
275
|
"model": model,
|
|
165
276
|
"max_tokens": max_tokens,
|
|
166
|
-
"messages":
|
|
277
|
+
"messages": messages,
|
|
167
278
|
}
|
|
168
279
|
payload = _merge_provider_settings(
|
|
169
280
|
base_payload,
|
|
@@ -176,11 +287,18 @@ def build_anthropic_reflection_payload(model: str, prompt: str, config: TextGepa
|
|
|
176
287
|
return payload
|
|
177
288
|
|
|
178
289
|
|
|
179
|
-
def
|
|
290
|
+
def build_anthropic_reflection_payload(model: str, prompt: str, config: TextGepaConfig) -> dict[str, Any]:
|
|
291
|
+
"""Preserve the frozen single-user prompt payload contract."""
|
|
292
|
+
return _build_anthropic_completion_payload(model, [{"role": "user", "content": prompt}], config)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _build_openai_completion_payload(
|
|
296
|
+
model: str, messages: list[dict[str, Any]], config: TextGepaConfig,
|
|
297
|
+
) -> dict[str, Any]:
|
|
180
298
|
_validate_reflection_max_tokens(config.reflection_max_tokens)
|
|
181
299
|
base_payload: dict[str, Any] = {
|
|
182
300
|
"model": model,
|
|
183
|
-
"input":
|
|
301
|
+
"input": messages,
|
|
184
302
|
}
|
|
185
303
|
if config.reflection_max_tokens is not None:
|
|
186
304
|
base_payload["max_output_tokens"] = config.reflection_max_tokens
|
|
@@ -192,6 +310,11 @@ def build_openai_reflection_payload(model: str, prompt: str, config: TextGepaCon
|
|
|
192
310
|
return _add_temperature(payload, config)
|
|
193
311
|
|
|
194
312
|
|
|
313
|
+
def build_openai_reflection_payload(model: str, prompt: str, config: TextGepaConfig) -> dict[str, Any]:
|
|
314
|
+
"""Preserve the frozen single-user prompt payload contract."""
|
|
315
|
+
return _build_openai_completion_payload(model, [{"role": "user", "content": prompt}], config)
|
|
316
|
+
|
|
317
|
+
|
|
195
318
|
def _extract_openai_output_text(data: dict[str, Any]) -> str:
|
|
196
319
|
output_text = data.get("output_text")
|
|
197
320
|
if isinstance(output_text, str):
|
|
@@ -208,72 +331,204 @@ def _extract_openai_output_text(data: dict[str, Any]) -> str:
|
|
|
208
331
|
return "".join(text_parts)
|
|
209
332
|
|
|
210
333
|
|
|
211
|
-
def
|
|
212
|
-
|
|
334
|
+
def _required_usage_int(usage: Any, field: str, provider: str) -> int:
|
|
335
|
+
if not isinstance(usage, dict):
|
|
336
|
+
raise ReflectionProviderError(
|
|
337
|
+
"ALI_1505_PROVIDER_USAGE_MISSING", f"{provider} reflection response omitted usage"
|
|
338
|
+
)
|
|
339
|
+
value = usage.get(field)
|
|
340
|
+
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
|
|
341
|
+
raise ReflectionProviderError(
|
|
342
|
+
"ALI_1505_PROVIDER_USAGE_INVALID", f"{provider} reflection response has invalid usage.{field}"
|
|
343
|
+
)
|
|
344
|
+
return value
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _optional_usage_int(usage: dict[str, Any], field: str, provider: str) -> int:
|
|
348
|
+
if field not in usage:
|
|
349
|
+
return 0
|
|
350
|
+
return _required_usage_int(usage, field, provider)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _anthropic_usage(data: dict[str, Any]) -> dict[str, int]:
|
|
354
|
+
usage = data.get("usage")
|
|
355
|
+
input_tokens = _required_usage_int(usage, "input_tokens", "Anthropic")
|
|
356
|
+
output_tokens = _required_usage_int(usage, "output_tokens", "Anthropic")
|
|
357
|
+
cache_input_tokens = (
|
|
358
|
+
_optional_usage_int(usage, "cache_creation_input_tokens", "Anthropic")
|
|
359
|
+
+ _optional_usage_int(usage, "cache_read_input_tokens", "Anthropic")
|
|
360
|
+
)
|
|
361
|
+
total_input_tokens = input_tokens + cache_input_tokens
|
|
362
|
+
# Anthropic's measured response has no total_tokens field. This exact sum
|
|
363
|
+
# is accounting arithmetic over its reported counts, never an estimate.
|
|
364
|
+
return {
|
|
365
|
+
"input_tokens": total_input_tokens,
|
|
366
|
+
"output_tokens": output_tokens,
|
|
367
|
+
"total_tokens": total_input_tokens + output_tokens,
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _openai_usage(data: dict[str, Any]) -> dict[str, int]:
|
|
372
|
+
usage = data.get("usage")
|
|
373
|
+
return {
|
|
374
|
+
"input_tokens": _required_usage_int(usage, "input_tokens", "OpenAI"),
|
|
375
|
+
"output_tokens": _required_usage_int(usage, "output_tokens", "OpenAI"),
|
|
376
|
+
"total_tokens": _required_usage_int(usage, "total_tokens", "OpenAI"),
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _provider_model(model: str, provider: str) -> str:
|
|
381
|
+
prefix = f"{provider}/"
|
|
382
|
+
return model.split("/", 1)[1] if model.startswith(prefix) else model
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _model_for_forced_provider(model: str, provider: str) -> str:
|
|
386
|
+
"""Keep direct ``reflect_with_<provider>`` calls on their historical provider."""
|
|
387
|
+
return model if model.startswith(f"{provider}/") else f"{provider}/{model}"
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def complete_reflection_messages(
|
|
391
|
+
*,
|
|
392
|
+
model: str,
|
|
393
|
+
messages: list[dict[str, Any]],
|
|
394
|
+
config: TextGepaConfig,
|
|
395
|
+
endpoint_override: str | None = None,
|
|
396
|
+
ssl_cert_file: str | None = None,
|
|
397
|
+
api_key_override: str | None = None,
|
|
398
|
+
) -> ProviderCompletion:
|
|
399
|
+
"""Complete explicit provider messages through the frozen sync transport.
|
|
400
|
+
|
|
401
|
+
This is deliberately transport-only: callers own their prompt construction
|
|
402
|
+
and candidate extraction. It preserves the existing retry, timeout, key,
|
|
403
|
+
and Anthropic curl-fallback policy while returning provider usage facts.
|
|
404
|
+
"""
|
|
405
|
+
provider = "openai" if model.startswith("openai/") else "anthropic"
|
|
406
|
+
provider_model = _provider_model(model, provider)
|
|
407
|
+
if endpoint_override is not None and api_key_override is None:
|
|
408
|
+
raise ReflectionProviderError(
|
|
409
|
+
"ALI_1505_ENDPOINT_OVERRIDE_API_KEY_REQUIRED",
|
|
410
|
+
"endpoint_override requires an explicit api_key_override",
|
|
411
|
+
)
|
|
412
|
+
endpoint = endpoint_override or (
|
|
413
|
+
"https://api.openai.com/v1/responses"
|
|
414
|
+
if provider == "openai"
|
|
415
|
+
else "https://api.anthropic.com/v1/messages"
|
|
416
|
+
)
|
|
417
|
+
# The loopback harness uses HTTP and deliberately synthetic certificate
|
|
418
|
+
# paths. Production HTTPS traffic receives the explicitly resolved CA.
|
|
419
|
+
try:
|
|
420
|
+
ssl_context = (
|
|
421
|
+
ssl.create_default_context(cafile=ssl_cert_file)
|
|
422
|
+
if ssl_cert_file and endpoint.startswith("https://")
|
|
423
|
+
else None
|
|
424
|
+
)
|
|
425
|
+
except (FileNotFoundError, ssl.SSLError) as error:
|
|
426
|
+
raise ReflectionProviderError(
|
|
427
|
+
"ALI_1505_SSL_CONTEXT_FAILED",
|
|
428
|
+
f"Reflection LM could not create an SSL context: {error}",
|
|
429
|
+
) from error
|
|
430
|
+
if provider == "anthropic":
|
|
431
|
+
api_key = api_key_override or os.environ.get("ANTHROPIC_API_KEY")
|
|
432
|
+
if not api_key:
|
|
433
|
+
raise ReflectionProviderError(
|
|
434
|
+
"ALI_1505_ANTHROPIC_API_KEY_MISSING", "ANTHROPIC_API_KEY is required for the default reflective LM"
|
|
435
|
+
)
|
|
436
|
+
payload = _build_anthropic_completion_payload(provider_model, messages, config)
|
|
437
|
+
request = urllib.request.Request(
|
|
438
|
+
endpoint,
|
|
439
|
+
data=json.dumps(payload).encode("utf-8"),
|
|
440
|
+
method="POST",
|
|
441
|
+
headers={
|
|
442
|
+
"x-api-key": api_key,
|
|
443
|
+
"anthropic-version": "2023-06-01",
|
|
444
|
+
"content-type": "application/json",
|
|
445
|
+
},
|
|
446
|
+
)
|
|
447
|
+
response = _read_json_response_with_retries(
|
|
448
|
+
request,
|
|
449
|
+
timeout_seconds=config.reflection_http_timeout_seconds,
|
|
450
|
+
retry_attempts=config.reflection_retry_attempts,
|
|
451
|
+
failure_prefix="Reflection LM failed",
|
|
452
|
+
ssl_fallback=(lambda: _read_anthropic_response_with_curl(api_key, payload)) if endpoint_override is None else None,
|
|
453
|
+
ssl_context=ssl_context,
|
|
454
|
+
)
|
|
455
|
+
parts = response.data.get("content") or []
|
|
456
|
+
text = "".join(part.get("text", "") for part in parts if isinstance(part, dict))
|
|
457
|
+
return ProviderCompletion(
|
|
458
|
+
text=text,
|
|
459
|
+
usage=_anthropic_usage(response.data),
|
|
460
|
+
request_id=_header_value(response.headers, "request-id"),
|
|
461
|
+
latency_ms=response.latency_ms,
|
|
462
|
+
provider=provider,
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
api_key = api_key_override or os.environ.get("OPENAI_API_KEY")
|
|
213
466
|
if not api_key:
|
|
214
|
-
raise
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
prompt = build_reflection_prompt(parent_text, parent_results, config)
|
|
219
|
-
payload = build_anthropic_reflection_payload(model, prompt, config)
|
|
467
|
+
raise ReflectionProviderError(
|
|
468
|
+
"ALI_1505_OPENAI_API_KEY_MISSING", "OPENAI_API_KEY is required for OpenAI reflection models"
|
|
469
|
+
)
|
|
470
|
+
payload = _build_openai_completion_payload(provider_model, messages, config)
|
|
220
471
|
request = urllib.request.Request(
|
|
221
|
-
|
|
472
|
+
endpoint,
|
|
222
473
|
data=json.dumps(payload).encode("utf-8"),
|
|
223
474
|
method="POST",
|
|
224
|
-
headers={
|
|
225
|
-
"x-api-key": api_key,
|
|
226
|
-
"anthropic-version": "2023-06-01",
|
|
227
|
-
"content-type": "application/json",
|
|
228
|
-
},
|
|
475
|
+
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
|
|
229
476
|
)
|
|
230
|
-
|
|
477
|
+
response = _read_json_response_with_retries(
|
|
231
478
|
request,
|
|
232
479
|
timeout_seconds=config.reflection_http_timeout_seconds,
|
|
233
480
|
retry_attempts=config.reflection_retry_attempts,
|
|
234
|
-
failure_prefix="
|
|
235
|
-
|
|
481
|
+
failure_prefix="OpenAI reflection LM failed",
|
|
482
|
+
ssl_context=ssl_context,
|
|
483
|
+
)
|
|
484
|
+
usage = _openai_usage(response.data)
|
|
485
|
+
text = _extract_openai_output_text(response.data)
|
|
486
|
+
if not text:
|
|
487
|
+
raise ReflectionProviderError(
|
|
488
|
+
"ALI_1505_OPENAI_OUTPUT_TEXT_MISSING",
|
|
489
|
+
"OpenAI reflection LM returned no output text",
|
|
490
|
+
usage=usage,
|
|
491
|
+
)
|
|
492
|
+
return ProviderCompletion(
|
|
493
|
+
text=text,
|
|
494
|
+
usage=usage,
|
|
495
|
+
request_id=_header_value(response.headers, "x-request-id"),
|
|
496
|
+
latency_ms=response.latency_ms,
|
|
497
|
+
provider=provider,
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def reflect_with_anthropic(parent_text: str, parent_results: list[RowEvaluation], config: TextGepaConfig) -> ReflectionResult:
|
|
502
|
+
prompt = build_reflection_prompt(parent_text, parent_results, config)
|
|
503
|
+
completion = complete_reflection_messages(
|
|
504
|
+
model=_model_for_forced_provider(config.reflection_model, "anthropic"),
|
|
505
|
+
messages=[{"role": "user", "content": prompt}],
|
|
506
|
+
config=config,
|
|
236
507
|
)
|
|
237
|
-
parts = data.get("content") or []
|
|
238
|
-
text = "".join(part.get("text", "") for part in parts if isinstance(part, dict))
|
|
239
508
|
return ReflectionResult(
|
|
240
509
|
prompt=prompt,
|
|
241
|
-
response=text,
|
|
242
|
-
candidate_text=extract_candidate_text(text),
|
|
510
|
+
response=completion.text,
|
|
511
|
+
candidate_text=extract_candidate_text(completion.text),
|
|
512
|
+
usage=completion.usage,
|
|
513
|
+
request_id=completion.request_id,
|
|
514
|
+
latency_ms=completion.latency_ms,
|
|
243
515
|
)
|
|
244
516
|
|
|
245
517
|
|
|
246
518
|
def reflect_with_openai(parent_text: str, parent_results: list[RowEvaluation], config: TextGepaConfig) -> ReflectionResult:
|
|
247
|
-
api_key = os.environ.get("OPENAI_API_KEY")
|
|
248
|
-
if not api_key:
|
|
249
|
-
raise RuntimeError("OPENAI_API_KEY is required for OpenAI reflection models")
|
|
250
|
-
model = config.reflection_model
|
|
251
|
-
if model.startswith("openai/"):
|
|
252
|
-
model = model.split("/", 1)[1]
|
|
253
519
|
prompt = build_reflection_prompt(parent_text, parent_results, config)
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
"
|
|
257
|
-
|
|
258
|
-
method="POST",
|
|
259
|
-
headers={
|
|
260
|
-
"Authorization": f"Bearer {api_key}",
|
|
261
|
-
"Content-Type": "application/json",
|
|
262
|
-
},
|
|
520
|
+
completion = complete_reflection_messages(
|
|
521
|
+
model=_model_for_forced_provider(config.reflection_model, "openai"),
|
|
522
|
+
messages=[{"role": "user", "content": prompt}],
|
|
523
|
+
config=config,
|
|
263
524
|
)
|
|
264
|
-
data = _read_json_response_with_retries(
|
|
265
|
-
request,
|
|
266
|
-
timeout_seconds=config.reflection_http_timeout_seconds,
|
|
267
|
-
retry_attempts=config.reflection_retry_attempts,
|
|
268
|
-
failure_prefix="OpenAI reflection LM failed",
|
|
269
|
-
)
|
|
270
|
-
text = _extract_openai_output_text(data)
|
|
271
|
-
if not text:
|
|
272
|
-
raise RuntimeError("OpenAI reflection LM returned no output text")
|
|
273
525
|
return ReflectionResult(
|
|
274
526
|
prompt=prompt,
|
|
275
|
-
response=text,
|
|
276
|
-
candidate_text=extract_candidate_text(text),
|
|
527
|
+
response=completion.text,
|
|
528
|
+
candidate_text=extract_candidate_text(completion.text),
|
|
529
|
+
usage=completion.usage,
|
|
530
|
+
request_id=completion.request_id,
|
|
531
|
+
latency_ms=completion.latency_ms,
|
|
277
532
|
)
|
|
278
533
|
|
|
279
534
|
|