feat(scripts): TimeWeb smoke test + bump default LLM timeout to 20s
scripts/check-timeweb.js — three probes: plain text, JSON mode, full
fast-advisor stack (registry injection + skill validation). Loads .env,
prints {ok, latency, reply preview} for each. Doesn't touch bot state.
Bumped DEFAULT_TIMEOUT_MS 8s → 20s in runtime/llm/provider.js. TimeWeb's
hosted agent endpoint takes 5-15s for the fast-advisor prompt
(registry block + snapshot context), so 8s was producing spurious
timeouts. OpenAI direct returns much faster; env var TIMEWEB_TIMEOUT_MS
overrides if needed.
Smoke verified live (PR #27 branch):
probe 1: 6.3s, plain prompt → "pepa hears you"
probe 2: 5.4s, JSON mode → {"alive":true,"name":"pepa"}
probe 3: 14.9s, advise() → action=switch_skill, skill=recovery.tunnel-out
(correct registered skill, sensible rationale — registry
injection successfully prevents hallucination)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -24,7 +24,11 @@ const ENV = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
const DEFAULT_BASE_URL = "https://api.openai.com/v1";
|
||||||
const DEFAULT_TIMEOUT_MS = 8000;
|
// 20s default — TimeWeb's hosted agent endpoint takes 5-15s for the
|
||||||
|
// fast-advisor prompt (registry block + snapshot context). 8s was too
|
||||||
|
// tight and produced spurious timeouts in smoke tests. OpenAI direct
|
||||||
|
// returns much faster (<2s); the env var overrides if needed.
|
||||||
|
const DEFAULT_TIMEOUT_MS = 20000;
|
||||||
|
|
||||||
export function isAvailable() {
|
export function isAvailable() {
|
||||||
return !!process.env[ENV.API_KEY];
|
return !!process.env[ENV.API_KEY];
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
// Smoke test for the TIMEWEB_* fast-LLM env vars.
|
||||||
|
//
|
||||||
|
// Loads .env, asks the model a tiny structured question, prints
|
||||||
|
// {ok, latency, code, first 200 chars of reply}. No bot state is
|
||||||
|
// touched — this is purely a connectivity check.
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// node scripts/check-timeweb.js
|
||||||
|
|
||||||
|
import { config as loadDotenv } from "dotenv";
|
||||||
|
loadDotenv();
|
||||||
|
|
||||||
|
import { complete, isAvailable, getConfig } from "../runtime/llm/provider.js";
|
||||||
|
import { advise, _resetForTest as resetAdvisor } from "../runtime/coach/fast-advisor.js";
|
||||||
|
|
||||||
|
function redact(key) {
|
||||||
|
if (!key) return "(unset)";
|
||||||
|
if (key.length < 12) return "(set, short)";
|
||||||
|
return `${key.slice(0, 6)}…${key.slice(-4)} (${key.length} chars)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log("=== TimeWeb / fast-advisor smoke test ===");
|
||||||
|
const cfg = getConfig();
|
||||||
|
console.log(`BASE_URL: ${cfg.baseUrl || "(unset)"}`);
|
||||||
|
console.log(`API_KEY: ${redact(cfg.apiKey)}`);
|
||||||
|
console.log(`MODEL: ${cfg.model || "(unset)"}`);
|
||||||
|
console.log(`TIMEOUT: ${cfg.timeoutMs}ms`);
|
||||||
|
console.log(`isAvailable: ${isAvailable()}`);
|
||||||
|
console.log("");
|
||||||
|
|
||||||
|
if (!isAvailable()) {
|
||||||
|
console.error("ERROR: TIMEWEB_API_KEY not set in .env — aborting.");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("→ probe 1: plain prompt, no JSON mode");
|
||||||
|
const r1 = await complete({
|
||||||
|
system: "Reply in 5 words or less.",
|
||||||
|
user: "Say 'pepa hears you'.",
|
||||||
|
json: false,
|
||||||
|
});
|
||||||
|
logResult(r1);
|
||||||
|
|
||||||
|
console.log("");
|
||||||
|
console.log("→ probe 2: JSON mode with a tiny structured request");
|
||||||
|
const r2 = await complete({
|
||||||
|
system: "Reply with strict JSON only.",
|
||||||
|
user: 'Return {"alive": true, "name": "pepa"}',
|
||||||
|
json: true,
|
||||||
|
});
|
||||||
|
logResult(r2);
|
||||||
|
|
||||||
|
console.log("");
|
||||||
|
console.log("→ probe 3: full fast-advisor stack (registry injection + skill validation)");
|
||||||
|
resetAdvisor();
|
||||||
|
const r3 = await advise({
|
||||||
|
snapshot: {
|
||||||
|
position: { x: 608, y: 90, z: 91 },
|
||||||
|
health: 14, food: 18, isDay: true,
|
||||||
|
inventory: { dirt: 4 },
|
||||||
|
activeSkill: "explore.far",
|
||||||
|
},
|
||||||
|
reason: "wedged_60s",
|
||||||
|
recentSkillIds: ["explore.far", "explore.far", "explore.far", "explore.far"],
|
||||||
|
lessonsTail: [
|
||||||
|
{ text: "Если позиция почти не меняется и инвентарь не растёт, прекращай текущий exploration skill." },
|
||||||
|
],
|
||||||
|
force: true,
|
||||||
|
});
|
||||||
|
console.log(` ok: ${r3.ok}`);
|
||||||
|
console.log(` latency: ${r3.latencyMs}ms`);
|
||||||
|
if (r3.ok) {
|
||||||
|
console.log(` action: ${r3.action}`);
|
||||||
|
console.log(` skillId: ${r3.skillId ?? "(n/a)"}`);
|
||||||
|
console.log(` why: ${r3.rationale}`);
|
||||||
|
} else {
|
||||||
|
console.log(` code: ${r3.code}`);
|
||||||
|
console.log(` detail: ${String(r3.detail).slice(0, 200)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function logResult(r) {
|
||||||
|
console.log(` ok: ${r.ok}`);
|
||||||
|
console.log(` latency: ${r.latencyMs}ms`);
|
||||||
|
if (!r.ok) {
|
||||||
|
console.log(` code: ${r.code}`);
|
||||||
|
console.log(` detail: ${String(r.detail).slice(0, 400)}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(` reply: ${typeof r.text === "string" ? r.text.slice(0, 200) : JSON.stringify(r.text).slice(0, 200)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((e) => {
|
||||||
|
console.error("UNHANDLED:", e?.message ?? e);
|
||||||
|
process.exit(2);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user