snapshot before v0.5 refactor
This commit is contained in:
Executable
+62
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env bash
|
||||
# Activate Deep Research environment (venv + secrets)
|
||||
# Usage: cd <project-root> && source scripts/activate.sh
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
echo "ERROR: use 'source', not 'bash':"
|
||||
echo " source scripts/activate.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Project root = directory containing pyproject.toml
|
||||
# Walk up from $PWD until we find it (or give up at /)
|
||||
_find_root() {
|
||||
local dir="${PWD}"
|
||||
while [[ "${dir}" != "/" ]]; do
|
||||
[[ -f "${dir}/pyproject.toml" ]] && echo "${dir}" && return 0
|
||||
dir="$(dirname "${dir}")"
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
_DR_ROOT="$(_find_root 2>/dev/null)"
|
||||
if [[ -z "${_DR_ROOT}" ]]; then
|
||||
echo "ERROR: pyproject.toml not found in ${PWD} or any parent."
|
||||
echo " cd to the project root first, then: source scripts/activate.sh"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# activate venv
|
||||
if [[ -f "${_DR_ROOT}/.venv/bin/activate" ]]; then
|
||||
source "${_DR_ROOT}/.venv/bin/activate"
|
||||
echo "venv: active ($(python --version 2>&1))"
|
||||
else
|
||||
echo "venv: not found - run: bash ${_DR_ROOT}/scripts/setup.sh"
|
||||
fi
|
||||
|
||||
# load secrets
|
||||
if [[ -f "${_DR_ROOT}/secrets.env" ]]; then
|
||||
set -a
|
||||
source "${_DR_ROOT}/secrets.env"
|
||||
set +a
|
||||
if [[ -n "${ZENMUX_API_KEY:-}" ]]; then
|
||||
echo "secrets: loaded (ZENMUX_API_KEY=${ZENMUX_API_KEY:0:12}...)"
|
||||
else
|
||||
echo "secrets: loaded but ZENMUX_API_KEY is empty"
|
||||
fi
|
||||
else
|
||||
echo "secrets: missing - run: cp secrets.env.example secrets.env"
|
||||
fi
|
||||
|
||||
# npm proxy fix: unset empty npm_config_proxy so npm inherits system http_proxy
|
||||
# without this, opencode crashes with "proxy.url must be a non-empty string"
|
||||
# when dynamically loading npm packages like @ai-sdk/anthropic
|
||||
if [[ -z "${npm_config_proxy:-}" ]]; then
|
||||
unset npm_config_proxy
|
||||
fi
|
||||
if [[ -z "${npm_config_https_proxy:-}" ]]; then
|
||||
unset npm_config_https_proxy
|
||||
fi
|
||||
|
||||
unset -f _find_root
|
||||
unset _DR_ROOT
|
||||
Executable
+147
@@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env bash
|
||||
# Deep Research - Environment setup (macOS + Debian/Ubuntu, using uv)
|
||||
# Usage: bash scripts/setup.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
CYAN='\033[0;36m'
|
||||
RST='\033[0m'
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
cd "${PROJECT_ROOT}"
|
||||
|
||||
echo -e "${CYAN}============================================${RST}"
|
||||
echo -e "${CYAN} Deep Research - Setup (uv)${RST}"
|
||||
echo -e "${CYAN} Project root: ${PROJECT_ROOT}${RST}"
|
||||
echo -e "${CYAN}============================================${RST}"
|
||||
|
||||
# -------- OS detection --------
|
||||
OS="unknown"
|
||||
case "$(uname -s)" in
|
||||
Darwin) OS="macos" ;;
|
||||
Linux)
|
||||
if [[ -f /etc/debian_version ]]; then
|
||||
OS="debian"
|
||||
elif [[ -f /etc/redhat-release ]]; then
|
||||
OS="rhel"
|
||||
else
|
||||
OS="linux"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
echo -e "${YELLOW}Detected OS: ${OS}${RST}"
|
||||
|
||||
# -------- [1/4] uv check / install --------
|
||||
echo ""
|
||||
echo -e "${CYAN}[1/4] Checking uv...${RST}"
|
||||
|
||||
if ! command -v uv >/dev/null 2>&1; then
|
||||
echo -e " ${YELLOW}uv not found. Installing...${RST}"
|
||||
echo -e " ${YELLOW}(uv = fast Python package manager written in Rust, by Astral)${RST}"
|
||||
echo ""
|
||||
read -r -p " Auto-install uv? [Y/n] " REPLY
|
||||
echo ""
|
||||
if [[ ! ${REPLY} =~ ^[Nn]$ ]]; then
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
# bring uv into current PATH
|
||||
[[ -f "${HOME}/.local/bin/uv" ]] && export PATH="${HOME}/.local/bin:${PATH}"
|
||||
if ! command -v uv >/dev/null 2>&1; then
|
||||
echo -e " ${RED}ERROR: uv still not found after install.${RST}"
|
||||
echo " Add ~/.local/bin to PATH, then re-run:"
|
||||
echo " export PATH=\"\${HOME}/.local/bin:\${PATH}\""
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo -e " ${RED}Manual install:${RST}"
|
||||
echo " macOS: brew install uv"
|
||||
echo " all: curl -LsSf https://astral.sh/uv/install.sh | sh"
|
||||
echo " pip: pip install --user uv"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
UV_VER="$(uv --version 2>&1 | head -1)"
|
||||
echo -e " ${GREEN}OK: ${UV_VER}${RST}"
|
||||
|
||||
# -------- [2/4] Python info --------
|
||||
echo ""
|
||||
echo -e "${CYAN}[2/4] Python check...${RST}"
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
SYS_PY="$(python3 --version 2>&1)"
|
||||
echo -e " ${GREEN}System: ${SYS_PY} (uv manages its own Python 3.10+ automatically)${RST}"
|
||||
else
|
||||
echo -e " ${YELLOW}python3 not found in PATH (uv will download Python automatically)${RST}"
|
||||
fi
|
||||
|
||||
# -------- [3/4] uv sync --------
|
||||
echo ""
|
||||
echo -e "${CYAN}[3/4] Syncing Python dependencies (uv sync)...${RST}"
|
||||
echo -e " ${YELLOW}First run may take a moment to download Python + packages.${RST}"
|
||||
uv sync
|
||||
echo -e " ${GREEN}OK: .venv/ ready${RST}"
|
||||
|
||||
# -------- [4/4] System binaries --------
|
||||
echo ""
|
||||
echo -e "${CYAN}[4/4] Checking system binaries...${RST}"
|
||||
|
||||
check_bin() {
|
||||
local name="${1}"
|
||||
local install_macos="${2}"
|
||||
local install_debian="${3}"
|
||||
if command -v "${name}" >/dev/null 2>&1; then
|
||||
local ver
|
||||
ver="$("${name}" --version 2>&1 | head -1)"
|
||||
echo -e " ${GREEN}OK: ${name} - ${ver}${RST}"
|
||||
else
|
||||
echo -e " ${YELLOW}MISSING: ${name}${RST}"
|
||||
case "${OS}" in
|
||||
macos) echo " Install: ${install_macos}" ;;
|
||||
debian) echo " Install: ${install_debian}" ;;
|
||||
*) echo " Install ${name} for your OS" ;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
check_bin "pandoc" "brew install pandoc" "sudo apt install pandoc"
|
||||
check_bin "opencode" "curl -fsSL https://opencode.ai/install | bash" "curl -fsSL https://opencode.ai/install | bash"
|
||||
check_bin "curl" "pre-installed" "sudo apt install curl"
|
||||
|
||||
# -------- Summary --------
|
||||
echo ""
|
||||
echo -e "${CYAN}============================================${RST}"
|
||||
echo -e "${CYAN} Setup complete${RST}"
|
||||
echo -e "${CYAN}============================================${RST}"
|
||||
echo ""
|
||||
|
||||
if [[ -f secrets.env ]]; then
|
||||
echo -e "${GREEN}OK: secrets.env found${RST}"
|
||||
else
|
||||
echo -e "${YELLOW}TODO: create secrets.env${RST}"
|
||||
echo " cp secrets.env.example secrets.env"
|
||||
echo " # fill in ZENMUX_API_KEY etc."
|
||||
fi
|
||||
|
||||
FONT_COUNT="$(find .opencode/templates/fonts -maxdepth 1 \( -name "*.otf" -o -name "*.ttf" \) 2>/dev/null | wc -l | tr -d ' ')"
|
||||
if [[ "${FONT_COUNT}" -ge 7 ]]; then
|
||||
echo -e "${GREEN}OK: fonts ready (${FONT_COUNT} files)${RST}"
|
||||
else
|
||||
echo -e "${YELLOW}TODO: download fonts (${FONT_COUNT}/7 present)${RST}"
|
||||
echo " bash .opencode/templates/fonts/download-fonts.sh"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. source scripts/activate.sh"
|
||||
echo " 2. bash .opencode/templates/fonts/download-fonts.sh"
|
||||
echo " 3. bash scripts/verify-zenmux.sh"
|
||||
echo " 4. opencode"
|
||||
echo ""
|
||||
echo "Tips:"
|
||||
echo " Run a script directly: uv run python .opencode/templates/report-template.py ..."
|
||||
echo " Add a package: uv add <package>"
|
||||
echo " Upgrade all: uv lock --upgrade && uv sync"
|
||||
echo " Full rebuild: rm -rf .venv && bash scripts/setup.sh"
|
||||
Executable
+113
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env bash
|
||||
# 验证 zenmux 两个端点 + prompt cache 是否可用
|
||||
# 用法:bash scripts/verify-zenmux.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# 加载密钥
|
||||
if [[ -f secrets.env ]]; then
|
||||
set -a; source secrets.env; set +a
|
||||
fi
|
||||
|
||||
if [[ -z "${ZENMUX_API_KEY:-}" ]]; then
|
||||
echo "❌ ZENMUX_API_KEY 未设置。请 cp secrets.env.example secrets.env 并填入 key"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "============================================"
|
||||
echo " zenmux 端点与 prompt cache 自检"
|
||||
echo "============================================"
|
||||
|
||||
# -------- 测试 1:OpenAI 兼容端点(Gemini) --------
|
||||
echo ""
|
||||
echo "[1/3] 测试 OpenAI 兼容端点(google/gemini-2.5-pro,隐式缓存)..."
|
||||
RESP1=$(curl -sS -w "\n---HTTP:%{http_code}---\n" -X POST "https://zenmux.ai/api/v1/chat/completions" \
|
||||
-H "Authorization: Bearer $ZENMUX_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "google/gemini-2.5-pro",
|
||||
"messages": [{"role": "user", "content": "用一句话回答:PubMed 是什么?"}],
|
||||
"max_tokens": 100
|
||||
}')
|
||||
echo "$RESP1" | tail -5
|
||||
echo ""
|
||||
|
||||
# -------- 测试 2:Anthropic 兼容端点(Claude Haiku) --------
|
||||
echo "[2/3] 测试 Anthropic 兼容端点(claude-haiku-4.5)..."
|
||||
RESP2=$(curl -sS -w "\n---HTTP:%{http_code}---\n" -X POST "https://zenmux.ai/api/anthropic/v1/messages" \
|
||||
-H "x-api-key: $ZENMUX_API_KEY" \
|
||||
-H "anthropic-version: 2023-06-01" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "claude-haiku-4.5",
|
||||
"max_tokens": 100,
|
||||
"messages": [{"role": "user", "content": "用一句话回答:PubMed 是什么?"}]
|
||||
}')
|
||||
echo "$RESP2" | tail -5
|
||||
echo ""
|
||||
|
||||
# -------- 测试 3:Claude + prompt cache(Sonnet) --------
|
||||
echo "[3/3] 测试 Claude prompt cache(连续 2 次请求,第 2 次应命中 cache)..."
|
||||
|
||||
# 构造一个至少 1024 tokens 的 system prompt(约 3000 字符中文足够)
|
||||
LONG_PROMPT=$(printf 'You are a biomedical research assistant. ' && \
|
||||
for i in $(seq 1 80); do
|
||||
printf 'This is test content line %d to build cacheable prefix context. ' "$i"
|
||||
done)
|
||||
|
||||
PAYLOAD=$(cat <<EOF
|
||||
{
|
||||
"model": "claude-haiku-4.5",
|
||||
"max_tokens": 50,
|
||||
"system": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "${LONG_PROMPT}",
|
||||
"cache_control": {"type": "ephemeral"}
|
||||
}
|
||||
],
|
||||
"messages": [{"role": "user", "content": "Say OK."}]
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
echo " 第 1 次调用(建 cache)..."
|
||||
R1=$(curl -sS -X POST "https://zenmux.ai/api/anthropic/v1/messages" \
|
||||
-H "x-api-key: $ZENMUX_API_KEY" \
|
||||
-H "anthropic-version: 2023-06-01" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$PAYLOAD")
|
||||
CACHE_CREATE=$(echo "$R1" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d.get('usage',{}).get('cache_creation_input_tokens', 0))" 2>/dev/null || echo "?")
|
||||
echo " cache_creation_input_tokens = $CACHE_CREATE"
|
||||
|
||||
sleep 2
|
||||
|
||||
echo " 第 2 次调用(应命中 cache)..."
|
||||
R2=$(curl -sS -X POST "https://zenmux.ai/api/anthropic/v1/messages" \
|
||||
-H "x-api-key: $ZENMUX_API_KEY" \
|
||||
-H "anthropic-version: 2023-06-01" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$PAYLOAD")
|
||||
CACHE_READ=$(echo "$R2" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d.get('usage',{}).get('cache_read_input_tokens', 0))" 2>/dev/null || echo "?")
|
||||
echo " cache_read_input_tokens = $CACHE_READ"
|
||||
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo " 结果"
|
||||
echo "============================================"
|
||||
|
||||
if [[ "$CACHE_CREATE" != "0" && "$CACHE_CREATE" != "?" ]]; then
|
||||
echo "✅ Cache 写入成功($CACHE_CREATE tokens)"
|
||||
else
|
||||
echo "⚠️ Cache 未写入(可能是 prompt 太短或端点不支持)"
|
||||
fi
|
||||
|
||||
if [[ "$CACHE_READ" != "0" && "$CACHE_READ" != "?" ]]; then
|
||||
echo "✅ Cache 命中成功($CACHE_READ tokens)— 成本大幅降低"
|
||||
else
|
||||
echo "⚠️ Cache 未命中(检查 AGENTS.md §6.5 诊断步骤)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "完整 usage 字段(第 2 次):"
|
||||
echo "$R2" | python3 -m json.tool 2>/dev/null | grep -A 10 '"usage"' || echo "$R2"
|
||||
Reference in New Issue
Block a user