now reports token usage
This commit is contained in:
parent
0497fadab8
commit
cd5776963f
117
askChatGPT
117
askChatGPT
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# askChatGPT - A simple Bash client for OpenAI Chat API
|
# askChatGPT - Bash client for OpenAI Chat API with token-usage to stderr
|
||||||
# Requires: curl, jq
|
# Requires: curl, jq
|
||||||
# Env var: OPENAI_API_KEY must be set
|
# Env: OPENAI_API_KEY must be set
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@ -9,7 +9,8 @@ MODEL="gpt-4o-mini"
|
|||||||
PREPEND_TEXT=""
|
PREPEND_TEXT=""
|
||||||
CONTENT_FILE=""
|
CONTENT_FILE=""
|
||||||
LIST_MODELS=0
|
LIST_MODELS=0
|
||||||
declare -a PROMPT_WORDS=() # <-- important: initialize the array
|
STREAM=0
|
||||||
|
declare -a PROMPT_WORDS=()
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
@ -20,42 +21,38 @@ Options:
|
|||||||
--list-models List available models from OpenAI
|
--list-models List available models from OpenAI
|
||||||
-prepend <text> Text to prepend before the main prompt
|
-prepend <text> Text to prepend before the main prompt
|
||||||
-content-from-file <file> Read prompt content from file
|
-content-from-file <file> Read prompt content from file
|
||||||
-h, --help Show this help message
|
--stream Stream output (still reports usage to stderr)
|
||||||
|
-h, --help Show this help
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
# Parse args
|
# Parse args
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-model)
|
-model) MODEL="$2"; shift 2 ;;
|
||||||
MODEL="$2"; shift 2 ;;
|
--list-models) LIST_MODELS=1; shift ;;
|
||||||
--list-models)
|
-prepend) PREPEND_TEXT="$2"; shift 2 ;;
|
||||||
LIST_MODELS=1; shift ;;
|
-content-from-file) CONTENT_FILE="$2"; shift 2 ;;
|
||||||
-prepend)
|
--stream) STREAM=1; shift ;;
|
||||||
PREPEND_TEXT="$2"; shift 2 ;;
|
-h|--help) usage; exit 0 ;;
|
||||||
-content-from-file)
|
*) PROMPT_WORDS+=("$1"); shift ;;
|
||||||
CONTENT_FILE="$2"; shift 2 ;;
|
|
||||||
-h|--help)
|
|
||||||
usage; exit 0 ;;
|
|
||||||
*)
|
|
||||||
PROMPT_WORDS+=("$1"); shift ;;
|
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
# Check deps
|
# Deps
|
||||||
for dep in curl jq; do
|
for dep in curl jq; do
|
||||||
command -v "$dep" >/dev/null 2>&1 || { echo "Error: '$dep' is required."; exit 1; }
|
command -v "$dep" >/dev/null 2>&1 || { echo "Error: '$dep' is required." >&2; exit 1; }
|
||||||
done
|
done
|
||||||
|
|
||||||
# Check API key
|
# API key
|
||||||
if [[ -z "${OPENAI_API_KEY:-}" ]]; then
|
if [[ -z "${OPENAI_API_KEY:-}" ]]; then
|
||||||
echo "Error: OPENAI_API_KEY is not set."
|
echo "Error: OPENAI_API_KEY is not set." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# List models
|
# List models
|
||||||
if [[ "$LIST_MODELS" -eq 1 ]]; then
|
if [[ "$LIST_MODELS" -eq 1 ]]; then
|
||||||
curl -s https://api.openai.com/v1/models \
|
curl -sS https://api.openai.com/v1/models \
|
||||||
-H "Authorization: Bearer $OPENAI_API_KEY" \
|
-H "Authorization: Bearer $OPENAI_API_KEY" \
|
||||||
| jq -r '.data[].id' | sort
|
| jq -r '.data[].id' | sort
|
||||||
exit 0
|
exit 0
|
||||||
@ -64,7 +61,7 @@ fi
|
|||||||
# Build prompt
|
# Build prompt
|
||||||
PROMPT_MAIN=""
|
PROMPT_MAIN=""
|
||||||
if [[ -n "$CONTENT_FILE" ]]; then
|
if [[ -n "$CONTENT_FILE" ]]; then
|
||||||
[[ -f "$CONTENT_FILE" ]] || { echo "Error: file '$CONTENT_FILE' not found."; exit 1; }
|
[[ -f "$CONTENT_FILE" ]] || { echo "Error: file '$CONTENT_FILE' not found." >&2; exit 1; }
|
||||||
PROMPT_MAIN="$(<"$CONTENT_FILE")"
|
PROMPT_MAIN="$(<"$CONTENT_FILE")"
|
||||||
elif (( ${#PROMPT_WORDS[@]} > 0 )); then
|
elif (( ${#PROMPT_WORDS[@]} > 0 )); then
|
||||||
PROMPT_MAIN="${PROMPT_WORDS[*]}"
|
PROMPT_MAIN="${PROMPT_WORDS[*]}"
|
||||||
@ -72,7 +69,7 @@ else
|
|||||||
if ! [ -t 0 ]; then
|
if ! [ -t 0 ]; then
|
||||||
PROMPT_MAIN="$(cat)"
|
PROMPT_MAIN="$(cat)"
|
||||||
else
|
else
|
||||||
echo "Error: No prompt provided."
|
echo "Error: No prompt provided." >&2
|
||||||
usage
|
usage
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@ -84,11 +81,14 @@ if [[ -n "$PREPEND_TEXT" ]]; then
|
|||||||
fi
|
fi
|
||||||
PROMPT_FULL+="$PROMPT_MAIN"
|
PROMPT_FULL+="$PROMPT_MAIN"
|
||||||
|
|
||||||
# JSON-escape content safely
|
|
||||||
JSON_CONTENT=$(printf '%s' "$PROMPT_FULL" | jq -Rs .)
|
JSON_CONTENT=$(printf '%s' "$PROMPT_FULL" | jq -Rs .)
|
||||||
|
|
||||||
# Send request
|
API_URL="https://api.openai.com/v1/chat/completions"
|
||||||
RESPONSE=$(curl -s https://api.openai.com/v1/chat/completions \
|
|
||||||
|
if [[ $STREAM -eq 0 ]]; then
|
||||||
|
# -------- Non-streaming ----------
|
||||||
|
RESPONSE=$(
|
||||||
|
curl -sS "$API_URL" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-H "Authorization: Bearer $OPENAI_API_KEY" \
|
-H "Authorization: Bearer $OPENAI_API_KEY" \
|
||||||
-d "$(cat <<EOF
|
-d "$(cat <<EOF
|
||||||
@ -99,9 +99,10 @@ RESPONSE=$(curl -s https://api.openai.com/v1/chat/completions \
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
)")
|
)"
|
||||||
|
)
|
||||||
|
|
||||||
# Extract and print answer (or error)
|
# Print assistant reply to stdout
|
||||||
echo "$RESPONSE" | jq -r '
|
echo "$RESPONSE" | jq -r '
|
||||||
if .choices and .choices[0].message.content then
|
if .choices and .choices[0].message.content then
|
||||||
.choices[0].message.content
|
.choices[0].message.content
|
||||||
@ -111,3 +112,63 @@ echo "$RESPONSE" | jq -r '
|
|||||||
"Error: No content in response"
|
"Error: No content in response"
|
||||||
end
|
end
|
||||||
'
|
'
|
||||||
|
|
||||||
|
# Print token usage to stderr (if present)
|
||||||
|
if [[ "$(echo "$RESPONSE" | jq 'has("usage")')" == "true" ]]; then
|
||||||
|
PT=$(echo "$RESPONSE" | jq -r '.usage.prompt_tokens // 0')
|
||||||
|
CT=$(echo "$RESPONSE" | jq -r '.usage.completion_tokens // 0')
|
||||||
|
TT=$(echo "$RESPONSE" | jq -r '.usage.total_tokens // 0')
|
||||||
|
echo "tokens: prompt=$PT completion=$CT total=$TT" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
# -------- Streaming ----------
|
||||||
|
# We request usage in the final streamed chunk
|
||||||
|
# Note: final chunk will have .usage populated and .choices == []
|
||||||
|
CURL_RESP=$(
|
||||||
|
curl -sS -N "$API_URL" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $OPENAI_API_KEY" \
|
||||||
|
-d "$(cat <<EOF
|
||||||
|
{
|
||||||
|
"model": "$MODEL",
|
||||||
|
"stream": true,
|
||||||
|
"stream_options": { "include_usage": true },
|
||||||
|
"messages": [
|
||||||
|
{"role": "user", "content": $JSON_CONTENT}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
)"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Process SSE lines
|
||||||
|
USAGE_PRINTED=0
|
||||||
|
# We’ll print content tokens as they come, and keep a trailing newline at the end
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[[ $line != data:* ]] && continue
|
||||||
|
payload="${line#data: }"
|
||||||
|
[[ "$payload" == "[DONE]" ]] && break
|
||||||
|
|
||||||
|
# Print incremental content (no newline, flush as it streams)
|
||||||
|
chunk_content=$(printf '%s' "$payload" | jq -r '.choices[0].delta.content // empty' 2>/dev/null || true)
|
||||||
|
if [[ -n "$chunk_content" ]]; then
|
||||||
|
printf "%s" "$chunk_content"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If this chunk carries usage, print to stderr
|
||||||
|
if [[ "$(printf '%s' "$payload" | jq 'has("usage")' 2>/dev/null || echo false)" == "true" ]]; then
|
||||||
|
PT=$(printf '%s' "$payload" | jq -r '.usage.prompt_tokens // 0')
|
||||||
|
CT=$(printf '%s' "$payload" | jq -r '.usage.completion_tokens // 0')
|
||||||
|
TT=$(printf '%s' "$payload" | jq -r '.usage.total_tokens // 0')
|
||||||
|
echo "" # ensure the streamed answer ends with newline on stdout
|
||||||
|
echo "tokens: prompt=$PT completion=$CT total=$TT" >&2
|
||||||
|
USAGE_PRINTED=1
|
||||||
|
fi
|
||||||
|
done <<< "$CURL_RESP"
|
||||||
|
|
||||||
|
# Safety: if for some reason usage didn’t arrive, still end with newline
|
||||||
|
if [[ $USAGE_PRINTED -eq 0 ]]; then
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user