Files
layue13 c6c2d071fb
Gatekeep evals / eval-floor (push) Successful in 3s
Gatekeep evals / eval-floor (pull_request) Successful in 15s
Gatekeep / gatekeep (pull_request) Failing after 1m46s
fix: harden partial 429 success, fallbacks, zh errors
Retry HTTP 429 with backoff; keep models beyond first 3 as
fallbacks; degrade agreement when some models fail; force-high
risk only if zero usable responses. Clearer zh-CN errors/docs for
dogfood uses: ./ rebase + workflow_dispatch re-run.
2026-10-27 08:35:52 +00:00

162 lines
5.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# Call one model via provider protocol.
# Protocols: openai | openai-completions | openai-responses | anthropic
# (openai / openai-compatible → chat completions)
# Retries HTTP 429 with exponential backoff (GATEKEEP_HTTP_RETRIES, default 3).
# Args: <provider> <model> <protocol> <base_url> <prompt.json> <out.json> <secrets.json>
set -euo pipefail
PROVIDER="${1:?provider}"
MODEL="${2:?model}"
PROTOCOL="${3:?protocol}"
BASE_URL="${4:?base_url}"
PROMPT_FILE="${5:?prompt}"
OUT_FILE="${6:?out}"
SECRETS_FILE="${7:?secrets}"
KEY=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get(sys.argv[2],""))' "$SECRETS_FILE" "$PROVIDER")
if [[ -z "$KEY" ]]; then
echo "missing key for $PROVIDER" >&2
echo "{\"error\":\"missing_key\",\"model\":\"$MODEL\",\"provider\":\"$PROVIDER\"}" > "$OUT_FILE"
exit 0
fi
MAX_TOKENS="${GATEKEEP_MAX_TOKENS:-4096}"
RETRIES="${GATEKEEP_HTTP_RETRIES:-3}"
SYSTEM=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["system"])' "$PROMPT_FILE")
USER=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["user"])' "$PROMPT_FILE")
LABEL="${PROVIDER}:${MODEL}"
# Normalize protocol aliases
WIRE=$(python3 -c 'import sys
p=sys.argv[1].lower()
m={"openai":"openai-completions","openai-compatible":"openai-completions",
"openai-completions":"openai-completions","openai-responses":"openai-responses",
"anthropic":"anthropic"}
print(m.get(p,""))' "$PROTOCOL")
if [[ -z "$WIRE" ]]; then
echo "{\"error\":\"bad_protocol\",\"protocol\":\"$PROTOCOL\",\"model\":\"$MODEL\"}" > "$OUT_FILE"
exit 0
fi
if [[ "$WIRE" == "openai-completions" ]]; then
BODY=$(python3 - "$MODEL" "$SYSTEM" "$USER" "$MAX_TOKENS" <<'PY'
import json,sys
model, system, user, max_tokens = sys.argv[1], sys.argv[2], sys.argv[3], int(sys.argv[4])
print(json.dumps({
"model": model,
"temperature": 0,
"max_tokens": max_tokens,
"messages": [
{"role": "system", "content": system},
{"role": "user", "content": user},
],
}))
PY
)
URL="${BASE_URL%/}/chat/completions"
EXTRA_HDR=(-H "HTTP-Referer: https://github.com/${GITHUB_REPOSITORY:-gatekeep}" -H "X-Title: Gatekeep")
AUTH=(-H "Authorization: Bearer $KEY")
EXTRACT=openai
elif [[ "$WIRE" == "openai-responses" ]]; then
BODY=$(python3 - "$MODEL" "$SYSTEM" "$USER" "$MAX_TOKENS" <<'PY'
import json,sys
model, system, user, max_tokens = sys.argv[1], sys.argv[2], sys.argv[3], int(sys.argv[4])
print(json.dumps({
"model": model,
"temperature": 0,
"max_output_tokens": max_tokens,
"input": [
{"role": "system", "content": system},
{"role": "user", "content": user},
],
}))
PY
)
URL="${BASE_URL%/}/responses"
EXTRA_HDR=()
AUTH=(-H "Authorization: Bearer $KEY")
EXTRACT=openai-responses
elif [[ "$WIRE" == "anthropic" ]]; then
BODY=$(python3 - "$MODEL" "$SYSTEM" "$USER" "$MAX_TOKENS" <<'PY'
import json,sys
model, system, user, max_tokens = sys.argv[1], sys.argv[2], sys.argv[3], int(sys.argv[4])
print(json.dumps({
"model": model,
"max_tokens": max_tokens,
"temperature": 0,
"system": system,
"messages": [{"role": "user", "content": user}],
}))
PY
)
URL="${BASE_URL%/}/v1/messages"
EXTRA_HDR=(-H "anthropic-version: 2023-06-01")
AUTH=(-H "x-api-key: $KEY")
EXTRACT=anthropic
fi
attempt=0
HTTP_CODE="000"
while true; do
HTTP_CODE=$(curl -sS -o "$OUT_FILE.raw" -w "%{http_code}" \
"$URL" \
"${AUTH[@]}" \
-H "Content-Type: application/json" \
"${EXTRA_HDR[@]}" \
-d "$BODY" || echo "000")
if [[ "$HTTP_CODE" == "200" ]]; then
break
fi
if [[ "$HTTP_CODE" == "429" && "$attempt" -lt "$RETRIES" ]]; then
sleep_s=$((2 ** attempt))
echo "$LABEL HTTP 429 — retry $((attempt + 1))/${RETRIES} after ${sleep_s}s" >&2
sleep "$sleep_s"
attempt=$((attempt + 1))
continue
fi
echo "$LABEL failed HTTP $HTTP_CODE" >&2
head -c 800 "$OUT_FILE.raw" >&2 || true
echo "{\"error\":\"http\",\"model\":\"$MODEL\",\"provider\":\"$PROVIDER\",\"http\":$HTTP_CODE}" > "$OUT_FILE"
exit 0
done
python3 - "$OUT_FILE.raw" "$OUT_FILE" "$MODEL" "$PROVIDER" "$EXTRACT" <<'PY'
import json,sys
raw=json.load(open(sys.argv[1]))
out, model, provider, kind = sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5]
if kind == "anthropic":
text = "".join(
(c.get("text") or "")
for c in (raw.get("content") or [])
if c.get("type") == "text"
)
elif kind == "openai-responses":
text = raw.get("output_text") or ""
if not text:
chunks = []
for item in raw.get("output") or []:
for c in item.get("content") or []:
if c.get("type") in ("output_text", "text"):
chunks.append(c.get("text") or "")
text = "".join(chunks)
else:
text = (((raw.get("choices") or [{}])[0].get("message") or {}).get("content")) or ""
start, end = text.find("{"), text.rfind("}")
payload = {}
if start >= 0 and end > start:
try:
payload = json.loads(text[start:end+1])
except Exception as e:
payload = {"parse_error": str(e), "raw_text": text[:2000]}
else:
payload = {"parse_error": "no_json", "raw_text": text[:2000]}
payload["_model"] = f"{provider}:{model}"
payload["_provider"] = provider
json.dump(payload, open(out,"w"), indent=2)
PY
echo "$LABEL → $OUT_FILE" >&2