failproofai 1.0.12-beta.2 → 1.0.12
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/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +4 -4
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/_0tovk6q0miud6._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3ycqv17cl._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4vkpxxu7._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08ch3staq3ejw._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bigvy9xmv2ka._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h86bl2qf0k_o._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pia5h3p8re4c._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pwj2h2efe5f-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1-ey9fgesfbc0._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15usvs-ms-0hf._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__00l2u6j86cb8y._.js → [root-of-the-server]__167mipqmjprq5._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16gb3_92zvk5x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1avi2kypusyb_._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1r7drlqli-ado._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bseae9u7g98r._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0eoura3_9gjb-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0mouhaflye1ro._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0pedwblqdoh58._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1fw0_nzkae5ao._.js → _1d98fyfrcaqhy._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1djqs9i27e4kg._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_20hn4gn87nsjc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo5_vm5f.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9snixb5fd-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47q0xq5w._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_components_project-list_tsx_1ke6anr0vt64h._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x33jqb6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpcz0uxur._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h47jmhhd._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_1fv03uigni8s5.js → node_modules_html-to-image_es_index_0tqc05n832v7a.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0janj0i6dqko7._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/package_json_[json]_cjs_013tntikdft59._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/preview-props.json +3 -3
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
- package/.next/standalone/.next/static/chunks/{1c_jf6g5wnslk.js → 0el9n3h9_zp5m.js} +1 -1
- package/.next/standalone/.next/static/chunks/0k7g_8x3o6gd-.js +1 -0
- package/.next/standalone/.next/static/chunks/{2o6d37j425uci.js → 0umzct31duk5g.js} +1 -1
- package/.next/standalone/.next/static/chunks/{42xptz88mtq3x.js → 0wobqgosirs8p.js} +1 -1
- package/.next/standalone/.next/static/chunks/12rug3jilwqzs.js +1 -0
- package/.next/standalone/.next/static/chunks/{0ay__-d2h1xb5.js → 1kduj74-715jh.js} +1 -1
- package/.next/standalone/.next/static/chunks/{40oworn_-x12p.js → 2701amhrbsbot.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0tbm3vjm62h-2.js → 27cavoc8paelg.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1my3-5p47t7r-.js → 2ve23a7keupjw.js} +1 -1
- package/.next/standalone/.next/static/chunks/{21hckpmsc4g70.js → 3bv6xaoitjpnh.js} +2 -2
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +19 -0
- package/.next/standalone/fp-cloud-cli/README.md +120 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/app.py +15 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +198 -7
- package/.next/standalone/fp-cloud-cli/fp_cli/cloud_output.py +523 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/_cloud.py +45 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/_write.py +3 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/account_cmds.py +171 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/agent_cmds.py +80 -7
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/alerts_cmds.py +23 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/audits_cmds.py +2 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/contracts_cmds.py +67 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/dashboards_cmds.py +159 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/evaluators_cmds.py +380 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/events_cmds.py +14 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +82 -6
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/list_cmds.py +4 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/orgs_cmds.py +17 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +143 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/queries_cmds.py +70 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/sentiment_cmds.py +49 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/sessions_cmds.py +95 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/settings_cmds.py +2 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/usage_cmds.py +21 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/users_cmds.py +14 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/errors.py +3 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/evaluator_spec.py +266 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/output.py +187 -81
- package/.next/standalone/fp-cloud-cli/fp_cli/theme.py +21 -24
- package/.next/standalone/fp-cloud-cli/skill/SKILL.md +124 -23
- package/.next/standalone/fp-cloud-cli/skill/agents/openai.yaml +2 -1
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +168 -16
- package/.next/standalone/fp-cloud-cli/tests/fake_cloud_smoke.py +312 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_account.py +125 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_agent.py +253 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_human_views.py +287 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_observability.py +116 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_transport.py +141 -0
- package/.next/standalone/fp-cloud-cli/tests/test_commands.py +3 -3
- package/.next/standalone/fp-cloud-cli/tests/test_dashboards_agent.py +3 -3
- package/.next/standalone/fp-cloud-cli/tests/test_evaluator_authoring.py +449 -0
- package/.next/standalone/fp-cloud-cli/tests/test_facets.py +1 -1
- package/.next/standalone/fp-cloud-cli/tests/test_help_table_coverage.py +15 -0
- package/.next/standalone/fp-cloud-cli/tests/test_operator.py +1 -1
- package/.next/standalone/fp-cloud-cli/tests/test_orgs.py +1 -1
- package/.next/standalone/fp-cloud-cli/tests/test_output.py +37 -37
- package/.next/standalone/fp-cloud-cli/tests/test_round4_behaviour.py +144 -0
- package/.next/standalone/fp-cloud-cli/tests/test_screen_language.py +80 -0
- package/.next/standalone/fp-cloud-cli/tests/test_session_reevaluate.py +33 -0
- package/.next/standalone/fp-cloud-cli/tests/test_staging_parity.py +198 -0
- package/.next/standalone/fp-cloud-cli/tests/test_whoami.py +1 -1
- package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
- package/.next/standalone/package.json +9 -9
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +68 -176
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +2 -2
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +4 -4
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package-lock.json +7 -7
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package-lock.json +7 -7
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +98 -115
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +3 -3
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package-lock.json +4 -4
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +4 -4
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package-lock.json +149 -171
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package.json +2 -2
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package-lock.json +4 -4
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +25 -25
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package-lock.json +7 -7
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package-lock.json +10 -10
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package-lock.json +7 -7
- package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package.json +1 -1
- package/.next/standalone/server.js +1 -1
- package/dist/cli.mjs +85 -37
- package/dist/worker.mjs +73 -28
- package/hermes-plugin/plugin.yaml +1 -1
- package/package.json +9 -9
- package/src/hooks/agent-skill.ts +3 -0
- package/src/hooks/fp-home.ts +1 -1
- package/src/hooks/handler.ts +22 -3
- package/src/hooks/semantic/intent.ts +127 -24
- package/src/hooks/semantic/jev-review.ts +21 -2
- package/.next/standalone/.next/static/chunks/20py8k1gd_i1m.js +0 -1
- package/.next/standalone/.next/static/chunks/2hmex93ppj1ke.js +0 -1
- /package/.next/standalone/.next/static/{LHCtFxVG0mdvd4lsXAOFC → D7LY1YSrw96ZxKSpxxrFw}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{LHCtFxVG0mdvd4lsXAOFC → D7LY1YSrw96ZxKSpxxrFw}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{LHCtFxVG0mdvd4lsXAOFC → D7LY1YSrw96ZxKSpxxrFw}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""Evaluator API bodies: validate data, never execute customer Python locally."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
import json
|
|
7
|
+
import math
|
|
8
|
+
import re
|
|
9
|
+
from typing import Any, Optional
|
|
10
|
+
|
|
11
|
+
from . import _click_compat as click
|
|
12
|
+
from .commands._write import read_text_arg
|
|
13
|
+
|
|
14
|
+
FIELDS = frozenset({
|
|
15
|
+
"eval_key", "display_name", "eval_version", "result_kind", "labels",
|
|
16
|
+
"condition_source", "evaluator_source", "timeout_seconds",
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def fail(message: str) -> None:
|
|
21
|
+
raise click.UsageError(message)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def text(value: Any, name: str) -> str:
|
|
25
|
+
if not isinstance(value, str) or not value.strip():
|
|
26
|
+
fail(f"{name} must be a non-empty string")
|
|
27
|
+
return value
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def threshold(value: Any) -> None:
|
|
31
|
+
if value is not None and (
|
|
32
|
+
isinstance(value, bool) or not isinstance(value, (int, float))
|
|
33
|
+
or not math.isfinite(value) or not 0 <= value <= 1
|
|
34
|
+
):
|
|
35
|
+
fail("threshold must be between 0 and 1")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def validate_labels(labels: Any) -> None:
|
|
39
|
+
if not isinstance(labels, list) or len(labels) > 20 or any(
|
|
40
|
+
not isinstance(v, str) or not v.strip() or len(v.encode()) > 64
|
|
41
|
+
or any(ord(c) < 32 or 127 <= ord(c) <= 159 for c in v) for v in labels
|
|
42
|
+
):
|
|
43
|
+
fail("labels must contain at most 20 non-empty strings, up to 64 bytes each, without control characters")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def source_kind(body: dict) -> str:
|
|
47
|
+
source = body.get("evaluator_source", "")
|
|
48
|
+
try:
|
|
49
|
+
envelope = json.loads(source)
|
|
50
|
+
except (ValueError, TypeError):
|
|
51
|
+
return "code"
|
|
52
|
+
return envelope.get("kind", "code") if isinstance(envelope, dict) else "code"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def binary_judge_step(step: str) -> bool:
|
|
56
|
+
"""Mirror the server's literal scoring check, plus natural-language pairs.
|
|
57
|
+
|
|
58
|
+
The six score phrases match evaluator_definitions.rs::steps_are_binary_scored.
|
|
59
|
+
Whitespace normalization and equivalent directives catch binary rubrics without
|
|
60
|
+
rejecting ordinary continuous wording such as "score between 0 and 1".
|
|
61
|
+
"""
|
|
62
|
+
lowered = " ".join(step.lower().split())
|
|
63
|
+
if any(phrase in lowered for phrase in (
|
|
64
|
+
"score 0", "score 1", "score of 0", "score of 1", "score: 0", "score: 1",
|
|
65
|
+
)):
|
|
66
|
+
return True
|
|
67
|
+
return bool(re.search(
|
|
68
|
+
r"\bbinary\b"
|
|
69
|
+
r"|\bscore(?:\s+of)?\s*[:=]?\s*(?:0|1|zero|one)\b"
|
|
70
|
+
r"|\b(?:yes\s*(?:/|or|and|versus|vs\.?|-)\s*no"
|
|
71
|
+
r"|no\s*(?:/|or|and|versus|vs\.?|-)\s*yes"
|
|
72
|
+
r"|true\s*(?:/|or|and|versus|vs\.?|-)\s*false"
|
|
73
|
+
r"|false\s*(?:/|or|and|versus|vs\.?|-)\s*true"
|
|
74
|
+
r"|pass\s*(?:/|or|and|versus|vs\.?|-)\s*fail"
|
|
75
|
+
r"|fail\s*(?:/|or|and|versus|vs\.?|-)\s*pass)\b"
|
|
76
|
+
r"|\b(?:give|assign|award|return|output)\s+"
|
|
77
|
+
r"(?:(?:a\s+)?(?:score|value)(?:\s+of)?\s*[:=]?\s*)?"
|
|
78
|
+
r"(?:0(?:\.0+)?|1(?:\.0+)?|zero|one|true|false|yes|no|pass|fail)"
|
|
79
|
+
r"\s+(?:if|when|otherwise|for)\b",
|
|
80
|
+
lowered,
|
|
81
|
+
))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def validate(body: dict, *, complete: bool = True) -> dict:
|
|
85
|
+
unknown = set(body) - FIELDS
|
|
86
|
+
if unknown:
|
|
87
|
+
fail(f"Unknown spec field(s): {', '.join(sorted(unknown))}; use the evaluator API body")
|
|
88
|
+
if complete:
|
|
89
|
+
key = text(body.get("eval_key"), "eval_key")
|
|
90
|
+
if not re.fullmatch(r"[a-z][a-z0-9_]*", key) or len(key.encode()) > 128:
|
|
91
|
+
fail("eval_key must match ^[a-z][a-z0-9_]*$")
|
|
92
|
+
name = text(body.get("display_name"), "display_name")
|
|
93
|
+
version = text(body.get("eval_version"), "eval_version")
|
|
94
|
+
if len(name.encode()) > 128 or len(version.encode()) > 128 or any(c.isspace() for c in version):
|
|
95
|
+
fail("display_name and eval_version are limited to 128 bytes; versions cannot contain whitespace")
|
|
96
|
+
elif body.get("eval_key") is not None and not re.fullmatch(r"[a-z][a-z0-9_]*", str(body["eval_key"])):
|
|
97
|
+
fail("eval_key must match ^[a-z][a-z0-9_]*$")
|
|
98
|
+
result_kind = body.get("result_kind", "score")
|
|
99
|
+
if result_kind not in ("score", "metric", "assertion"):
|
|
100
|
+
fail("result_kind must be score, metric or assertion")
|
|
101
|
+
labels = body.get("labels", [])
|
|
102
|
+
validate_labels(labels)
|
|
103
|
+
timeout = body.get("timeout_seconds")
|
|
104
|
+
if timeout is not None and (
|
|
105
|
+
isinstance(timeout, bool) or not isinstance(timeout, (int, float))
|
|
106
|
+
or not math.isfinite(timeout) or not 0.001 <= timeout <= 3600
|
|
107
|
+
):
|
|
108
|
+
fail("timeout_seconds must be finite and between 0.001 and 3600")
|
|
109
|
+
condition = body.get("condition_source")
|
|
110
|
+
if condition is not None:
|
|
111
|
+
text(condition, "condition_source")
|
|
112
|
+
if len(condition.encode()) > 16 * 1024:
|
|
113
|
+
fail("condition_source is limited to 16 KiB")
|
|
114
|
+
expression(condition, "condition_source")
|
|
115
|
+
source = text(body.get("evaluator_source"), "evaluator_source")
|
|
116
|
+
if len(source.encode()) > 128 * 1024:
|
|
117
|
+
fail("evaluator_source is limited to 128 KiB")
|
|
118
|
+
kind = source_kind(body)
|
|
119
|
+
if kind == "code":
|
|
120
|
+
expression(source, "evaluator_source")
|
|
121
|
+
return body
|
|
122
|
+
envelope = json.loads(source)
|
|
123
|
+
if kind not in ("judge", "jev"):
|
|
124
|
+
fail("evaluator_source kind must be judge or jev")
|
|
125
|
+
if result_kind != "score":
|
|
126
|
+
fail("judge/Jev evaluators require result_kind score")
|
|
127
|
+
threshold(envelope.get("threshold"))
|
|
128
|
+
if kind == "judge":
|
|
129
|
+
text(envelope.get("criteria"), "judge criteria")
|
|
130
|
+
steps = envelope.get("steps")
|
|
131
|
+
if steps is not None:
|
|
132
|
+
if not isinstance(steps, list):
|
|
133
|
+
fail("judge steps must be a list")
|
|
134
|
+
for step in steps:
|
|
135
|
+
text(step, "judge step")
|
|
136
|
+
if binary_judge_step(step):
|
|
137
|
+
fail("binary judge steps are not supported; use graded evaluation steps")
|
|
138
|
+
else:
|
|
139
|
+
text(envelope.get("instructions"), "Jev instructions")
|
|
140
|
+
criteria = envelope.get("criteria")
|
|
141
|
+
if envelope.get("type") == "noul":
|
|
142
|
+
if not isinstance(criteria, dict) or set(criteria) != {"true", "false"}:
|
|
143
|
+
fail("Jev noul criteria must contain true and false")
|
|
144
|
+
text(criteria["true"], "true criterion")
|
|
145
|
+
text(criteria["false"], "false criterion")
|
|
146
|
+
if criteria["true"].strip() == criteria["false"].strip():
|
|
147
|
+
fail("Jev true and false criteria must be distinct")
|
|
148
|
+
elif envelope.get("type") == "score":
|
|
149
|
+
if not isinstance(criteria, list) or not 3 <= len(criteria) <= 5:
|
|
150
|
+
fail("Jev score needs 3–5 distinct levels, ordered worst to best")
|
|
151
|
+
for level in criteria:
|
|
152
|
+
text(level, "Jev level")
|
|
153
|
+
if len({v.strip().lower() for v in criteria}) != len(criteria):
|
|
154
|
+
fail("Jev score levels must be distinct")
|
|
155
|
+
else:
|
|
156
|
+
fail("Jev type must be noul or score")
|
|
157
|
+
return body
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def from_draft(data: dict, requested_kind: str) -> dict:
|
|
161
|
+
"""Convert the dashboard's EvalDraft (UI fields) to the immutable API body."""
|
|
162
|
+
draft = data.get("spec", data)
|
|
163
|
+
if isinstance(draft, str):
|
|
164
|
+
try:
|
|
165
|
+
draft = json.loads(draft)
|
|
166
|
+
except ValueError:
|
|
167
|
+
fail("Cloud draft was not an evaluator JSON spec")
|
|
168
|
+
if not isinstance(draft, dict):
|
|
169
|
+
fail("Cloud draft was not an evaluator JSON spec")
|
|
170
|
+
body = {k: v for k, v in draft.items() if k in FIELDS}
|
|
171
|
+
if not body.get("condition_source"):
|
|
172
|
+
body.pop("condition_source", None)
|
|
173
|
+
body.setdefault("eval_version", "1")
|
|
174
|
+
kind = draft.get("kind", source_kind(body))
|
|
175
|
+
if kind != requested_kind:
|
|
176
|
+
fail(f"Cloud drafted {kind}, not requested {requested_kind}; retry with a more specific intent")
|
|
177
|
+
if kind == "judge" and source_kind(body) != "judge":
|
|
178
|
+
body["evaluator_source"] = json.dumps({
|
|
179
|
+
"kind": "judge", "criteria": draft.get("criteria"),
|
|
180
|
+
"threshold": draft.get("threshold"), "steps": draft.get("steps"),
|
|
181
|
+
})
|
|
182
|
+
elif kind == "jev" and source_kind(body) != "jev":
|
|
183
|
+
body["evaluator_source"] = json.dumps({
|
|
184
|
+
"kind": "jev", "type": draft.get("jevType"), "instructions": draft.get("instructions"),
|
|
185
|
+
"criteria": draft.get("jevCriteria"), "threshold": draft.get("threshold"),
|
|
186
|
+
})
|
|
187
|
+
return validate(body)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def expression(source: str, name: str) -> None:
|
|
191
|
+
try:
|
|
192
|
+
ast.parse(source.strip(), mode="eval")
|
|
193
|
+
except SyntaxError as exc:
|
|
194
|
+
fail(f"{name} must be a restricted Python expression: {exc.msg}")
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def build(
|
|
198
|
+
*, key=None, name=None, version=None, label=None, condition=None,
|
|
199
|
+
code=None, result_kind=None, timeout=None, judge=False, criteria=None,
|
|
200
|
+
step=None, threshold_value=None, jev=None, instructions=None,
|
|
201
|
+
true=None, false=None, level=None, file=None, complete=True,
|
|
202
|
+
) -> dict:
|
|
203
|
+
kinds = sum((bool(code), bool(judge), bool(jev), bool(file)))
|
|
204
|
+
if kinds != 1:
|
|
205
|
+
fail("Choose exactly one source: --code, --judge, --jev or --file")
|
|
206
|
+
source_flags = (criteria, step, threshold_value, instructions, true, false, level)
|
|
207
|
+
if file:
|
|
208
|
+
if any(v is not None for v in (key, name, version, label, condition, result_kind, timeout, *source_flags)):
|
|
209
|
+
fail("Pass either --file or discrete spec flags, not both")
|
|
210
|
+
try:
|
|
211
|
+
body = json.loads(read_text_arg(file))
|
|
212
|
+
except ValueError as exc:
|
|
213
|
+
fail(f"--file is not valid JSON: {exc}")
|
|
214
|
+
if not isinstance(body, dict):
|
|
215
|
+
fail("--file must contain a JSON object")
|
|
216
|
+
return validate(body, complete=complete)
|
|
217
|
+
if code:
|
|
218
|
+
if any(v is not None for v in source_flags):
|
|
219
|
+
fail("Judge/Jev flags cannot be used with --code")
|
|
220
|
+
source = read_text_arg(code, flag="--code")
|
|
221
|
+
elif judge:
|
|
222
|
+
if any(v is not None for v in (instructions, true, false, level)):
|
|
223
|
+
fail("Jev flags cannot be used with --judge")
|
|
224
|
+
source = json.dumps({"kind": "judge", "criteria": criteria, "threshold": threshold_value, "steps": step})
|
|
225
|
+
else:
|
|
226
|
+
if criteria is not None or step is not None:
|
|
227
|
+
fail("Judge flags cannot be used with --jev")
|
|
228
|
+
if jev == "noul" and level is not None or jev == "score" and (true is not None or false is not None):
|
|
229
|
+
fail("Use --true/--false for Jev noul; --level for Jev score")
|
|
230
|
+
source = json.dumps({
|
|
231
|
+
"kind": "jev", "type": jev, "instructions": instructions,
|
|
232
|
+
"criteria": {"true": true, "false": false} if jev == "noul" else level,
|
|
233
|
+
"threshold": threshold_value,
|
|
234
|
+
})
|
|
235
|
+
body = {
|
|
236
|
+
"result_kind": result_kind or "score", "labels": label or [],
|
|
237
|
+
"evaluator_source": source,
|
|
238
|
+
}
|
|
239
|
+
if key is not None:
|
|
240
|
+
body["eval_key"] = key
|
|
241
|
+
if name is not None:
|
|
242
|
+
body["display_name"] = name
|
|
243
|
+
if version is not None:
|
|
244
|
+
body["eval_version"] = version
|
|
245
|
+
if condition is not None:
|
|
246
|
+
body["condition_source"] = read_text_arg(condition[1:], flag="--condition") if condition.startswith("@") else condition
|
|
247
|
+
if timeout is not None:
|
|
248
|
+
body["timeout_seconds"] = timeout
|
|
249
|
+
if complete:
|
|
250
|
+
body.setdefault("eval_version", "1")
|
|
251
|
+
return validate(body, complete=complete)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def dry_run_body(body: dict, sessions: Optional[list]) -> dict:
|
|
255
|
+
if len(sessions or []) > 10:
|
|
256
|
+
fail("dry-run accepts at most 10 sessions")
|
|
257
|
+
if any(not isinstance(s, str) or not s.strip() or len(s.encode()) > 256 for s in (sessions or [])):
|
|
258
|
+
fail("dry-run session ids must be non-empty, up to 256 bytes")
|
|
259
|
+
if source_kind(body) == "judge":
|
|
260
|
+
fail("Cloud refuses judge dry-runs; create/deploy without --dry-run-on and inspect evaluation results instead")
|
|
261
|
+
return {
|
|
262
|
+
**{k: v for k, v in body.items() if k in (
|
|
263
|
+
"evaluator_source", "condition_source", "timeout_seconds", "result_kind", "eval_key",
|
|
264
|
+
)},
|
|
265
|
+
"session_ids": sessions or [],
|
|
266
|
+
}
|