#!/usr/bin/env bash set -uo pipefail # kein -e: wir behandeln Fehler selbst # @describe Perform a web search using Tavily API. # NOTE: This tool NEVER exits non-zero. It reports errors in output. # @option --query The query to search for. # @env TAVILY_API_KEY The api key # @env LLM_OUTPUT=/dev/stdout The output path OUT="${LLM_OUTPUT:-/dev/stdout}" MAX_CHARS="${LLM_WEB_QUERY_MAX_CHARS:-380}" MAX_RESULTS="${LLM_WEB_MAX_RESULTS:-5}" err() { echo "ERROR: $*" >> "$OUT" exit 0 } sanitize_query() { local q="$1" q="$(printf "%s" "$q" | tr '\r\n\t' ' ' | sed -E 's/^[[:space:]]+|[[:space:]]+$//g; s/[[:space:]]+/ /g')" q="${q#:}" q="$(printf "%s" "$q" | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')" q="${q:0:$MAX_CHARS}" printf "%s" "$q" } main() { local query="${argc_query:-}" local api_key="${TAVILY_API_KEY:-}" [[ -z "$query" ]] && err "missing --query" [[ -z "$api_key" ]] && err "missing TAVILY_API_KEY" query="$(sanitize_query "$query")" [[ -z "$query" ]] && err "query empty after sanitization" local payload payload="$(jq -n \ --arg api_key "$api_key" \ --arg query "$query" \ --argjson max_results "$MAX_RESULTS" \ '{ api_key: $api_key, query: $query, include_answer: true, max_results: $max_results, search_depth: "basic" }' 2>/tmp/web_payload.err)" || { err "jq failed building payload: $(tr '\n' ' ' < /tmp/web_payload.err)" } local curl_err resp curl_rc http_code curl_err="$(mktemp)" resp="$( curl -sS -L --connect-timeout 10 --max-time 25 \ --retry 2 --retry-all-errors --retry-delay 0 \ -X POST "https://api.tavily.com/search" \ -H "content-type: application/json" \ --data-binary "$payload" \ -w "\n__HTTP_CODE__:%{http_code}\n" \ 2>"$curl_err" )" curl_rc=$? if [[ $curl_rc -ne 0 ]]; then err "curl failed (exit_code=$curl_rc): $(tr '\n' ' ' < "$curl_err")" fi http_code="$(printf "%s" "$resp" | sed -n 's/^__HTTP_CODE__:\([0-9]\{3\}\)$/\1/p' | tail -n 1)" resp="$(printf "%s" "$resp" | sed '/^__HTTP_CODE__:/d')" [[ -z "$http_code" ]] && err "missing http_code (unexpected curl output)" if [[ "$http_code" != "200" ]]; then echo "ERROR: http_status=$http_code" >> "$OUT" echo "BODY_SNIPPET:" >> "$OUT" printf "%s" "$resp" | head -c 2000 >> "$OUT" echo >> "$OUT" exit 0 fi local jq_out jq_rc jq_out="$(printf "%s" "$resp" | jq -r ' def clip(n): tostring | gsub("[\r\n\t]+";" ") | gsub(" +";" ") | .[0:n]; . as $r | ($r.answer // "") as $a | ($r.results // []) as $rs | "QUERY: " + ($r.query // ""), (if ($a|length)>0 then "ANSWER: " + ($a|clip(1200)) else "ANSWER: " + "" end), "RESULTS:", ($rs[0:5] | to_entries[] | "- " + (.value.title // "" | clip(200)) + " | " + (.value.url // "") + (if (.value.content // "") != "" then " | " + (.value.content|clip(240)) else "" end)) ' 2>&1)" jq_rc=$? if [[ $jq_rc -ne 0 ]]; then echo "ERROR: jq parse failed (exit_code=$jq_rc): $jq_out" >> "$OUT" echo "BODY_SNIPPET:" >> "$OUT" printf "%s" "$resp" | head -c 2000 >> "$OUT" echo >> "$OUT" exit 0 fi if grep -q '^ANSWER: *$' <<<"$jq_out" && ! grep -q '^- ' <<<"$jq_out"; then echo "ERROR: empty answer and no results (try different query)" >> "$OUT" echo "DEBUG_BODY_SNIPPET:" >> "$OUT" printf "%s" "$resp" | head -c 2000 >> "$OUT" echo >> "$OUT" exit 0 fi printf "%s\n" "$jq_out" >> "$OUT" exit 0 } # argc parse: erst Code holen, dann eval -> argc ruft main() auf argc_eval="$(argc --argc-eval "$0" "$@" 2>/tmp/argc_web_search.err)" || { err "argc parse failed: $(tr '\n' ' ' < /tmp/argc_web_search.err)" } eval "$argc_eval"