Files
gatekeep/scripts/call-model.sh
T
layue13 05f8fad38a
Gatekeep evals / eval-floor (push) Successful in 8s
Dogfood: run Gatekeep on this repo's PRs
Add .github/workflows/gatekeep-pr.yml with uses: ./ and
OPENROUTER_API_KEY from secrets. Forge-agnostic API helper for Gitea.
2026-10-26 20:40:51 +00:00

152 lines
4.9 KiB
Bash

#!/usr/bin/env bash
# Call one model via provider protocol.
# Protocols: openai | openai-completions | openai-responses | anthropic
# (openai / openai-compatible → chat completions)
# 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}"
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
)
HTTP_CODE=$(curl -sS -o "$OUT_FILE.raw" -w "%{http_code}" \
"${BASE_URL%/}/chat/completions" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-H "HTTP-Referer: https://github.com/${GITHUB_REPOSITORY:-gatekeep}" \
-H "X-Title: Gatekeep" \
-d "$BODY")
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
)
HTTP_CODE=$(curl -sS -o "$OUT_FILE.raw" -w "%{http_code}" \
"${BASE_URL%/}/responses" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d "$BODY")
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
)
HTTP_CODE=$(curl -sS -o "$OUT_FILE.raw" -w "%{http_code}" \
"${BASE_URL%/}/v1/messages" \
-H "x-api-key: $KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d "$BODY")
EXTRACT=anthropic
fi
if [[ "$HTTP_CODE" != "200" ]]; then
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
fi
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":
# Best-effort extract from Responses API
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