llm.hanzo.ai is dead — api.hanzo.ai is the one gateway; harden live HF tests
Migrate the last llm.hanzo.ai references (vscode MCP llm tool provider URL + gateway fallback, auth-chat e2e doc comments) to the canonical https://api.hanzo.ai/v1 (OpenAI-compatible, IAM JWT at the gateway — see hanzoai/ai). The browser extension chat paths already pointed at API_BASE_URL = api.hanzo.ai. model-hub's HuggingFace tests are real live-API integration tests, which means they fail on HF's weather: repeated runs rate-limit and the 5s default timeout reads as 6 test failures with nothing of ours broken. Probe HF once at module load and describe.skipIf with the status logged when HF itself refuses service; give live calls a 15s budget. A failure while HF answers 200 is still a real failure. 243/243 across repeated runs.
This commit is contained in:
@@ -8,11 +8,11 @@ import { test, expect } from './fixtures';
|
||||
* 1. Login tab opens correctly
|
||||
* 2. Password auth succeeds
|
||||
* 3. Token is stored and user info is fetched
|
||||
* 4. Chat sends a message to a Zen model via llm.hanzo.ai
|
||||
* 4. Chat sends a message to a Zen model via api.hanzo.ai
|
||||
*
|
||||
* Requirements:
|
||||
* - IAM at iam.hanzo.ai / hanzo.id must be reachable
|
||||
* - LLM gateway at llm.hanzo.ai must be reachable
|
||||
* - LLM gateway at api.hanzo.ai must be reachable
|
||||
* - Extension must be built: npm run build
|
||||
*/
|
||||
|
||||
|
||||
@@ -81,9 +81,23 @@ describe('searchOllamaLibrary', () => {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// HuggingFace API integration tests (real network calls)
|
||||
//
|
||||
// These verify OUR client against the live API. When HF itself refuses
|
||||
// service (rate-limit 429, outage, no network) there is nothing of ours to
|
||||
// verify — skip with the status logged instead of failing on the weather.
|
||||
// Any failure while HF answers 200 is still a real failure. Live calls get
|
||||
// a 15s budget (HF routinely exceeds vitest's 5s default under load).
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('HuggingFace API', () => {
|
||||
const HF_LIVE_TIMEOUT = 15_000;
|
||||
const hfStatus = await fetch('https://huggingface.co/api/models?limit=1', {
|
||||
signal: AbortSignal.timeout(5000),
|
||||
}).then((r) => r.status, () => 0);
|
||||
if (hfStatus !== 200) {
|
||||
console.warn(`[model-hub.test] skipping live HF tests — huggingface.co status ${hfStatus || 'unreachable'}`);
|
||||
}
|
||||
|
||||
describe.skipIf(hfStatus !== 200)('HuggingFace API', () => {
|
||||
it('searchHuggingFace returns models', async () => {
|
||||
const models = await searchHuggingFace('llama', { limit: 2 });
|
||||
expect(models.length).toBeGreaterThan(0);
|
||||
@@ -92,7 +106,7 @@ describe('HuggingFace API', () => {
|
||||
expect(m.id).toBeTruthy();
|
||||
expect(m.source).toBe('huggingface');
|
||||
}
|
||||
});
|
||||
}, HF_LIVE_TIMEOUT);
|
||||
|
||||
it('searchGGUF returns GGUF-tagged models', async () => {
|
||||
const models = await searchGGUF('qwen', 3);
|
||||
@@ -100,12 +114,12 @@ describe('HuggingFace API', () => {
|
||||
for (const m of models) {
|
||||
expect(m.tags).toBeInstanceOf(Array);
|
||||
}
|
||||
});
|
||||
}, HF_LIVE_TIMEOUT);
|
||||
|
||||
it('searchMLX returns MLX models', async () => {
|
||||
const models = await searchMLX('llama', 3);
|
||||
expect(models.length).toBeGreaterThan(0);
|
||||
});
|
||||
}, HF_LIVE_TIMEOUT);
|
||||
|
||||
it('getHuggingFaceModel returns model with files', async () => {
|
||||
// Use a well-known public model that doesn't require auth
|
||||
@@ -124,18 +138,18 @@ describe('HuggingFace API', () => {
|
||||
).toBe(true);
|
||||
expect(f.downloadUrl).toContain('huggingface.co');
|
||||
}
|
||||
});
|
||||
}, HF_LIVE_TIMEOUT);
|
||||
|
||||
it('getModelCard returns markdown', async () => {
|
||||
const card = await getModelCard('TheBloke/Llama-2-7B-Chat-GGUF');
|
||||
expect(typeof card).toBe('string');
|
||||
// Card may be empty for some models; just verify it's a string
|
||||
expect(card).toBeDefined();
|
||||
});
|
||||
}, HF_LIVE_TIMEOUT);
|
||||
|
||||
it('getModelStats returns download count', async () => {
|
||||
const stats = await getModelStats('TheBloke/Llama-2-7B-Chat-GGUF');
|
||||
expect(stats.downloads).toBeGreaterThan(0);
|
||||
expect(stats.tags).toBeInstanceOf(Array);
|
||||
});
|
||||
}, HF_LIVE_TIMEOUT);
|
||||
});
|
||||
|
||||
@@ -32,7 +32,7 @@ function detectProviders(): ProviderConfig[] {
|
||||
const found: ProviderConfig[] = [];
|
||||
if (process.env.OPENAI_API_KEY) found.push({ name: 'OpenAI', url: 'https://api.openai.com/v1', models: ['gpt-4o', 'gpt-4o-mini', 'o3-mini'] });
|
||||
if (process.env.ANTHROPIC_API_KEY) found.push({ name: 'Anthropic', url: 'https://api.anthropic.com', models: ['claude-opus-4-6', 'claude-sonnet-4-6'] });
|
||||
if (process.env.HANZO_API_KEY) found.push({ name: 'Hanzo', url: 'https://llm.hanzo.ai/v1', models: ['zen-400b', 'zen-70b'] });
|
||||
if (process.env.HANZO_API_KEY) found.push({ name: 'Hanzo', url: 'https://api.hanzo.ai/v1', models: ['zen-400b', 'zen-70b'] });
|
||||
try { execSync('curl -sf http://localhost:11434/api/tags', { timeout: 2000, stdio: 'pipe' }); found.push({ name: 'Ollama', url: 'http://localhost:11434' }); } catch {}
|
||||
try { execSync('curl -sf http://localhost:1234/v1/models', { timeout: 2000, stdio: 'pipe' }); found.push({ name: 'LM Studio', url: 'http://localhost:1234/v1' }); } catch {}
|
||||
return found;
|
||||
@@ -71,7 +71,7 @@ export function createLLMTools(context: vscode.ExtensionContext): MCPTool[] {
|
||||
|
||||
// Route to Hanzo LLM gateway (unified proxy for all providers)
|
||||
const apiKey = process.env.HANZO_API_KEY || process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY;
|
||||
const baseUrl = process.env.HANZO_LLM_URL || 'https://llm.hanzo.ai/v1';
|
||||
const baseUrl = process.env.HANZO_LLM_URL || 'https://api.hanzo.ai/v1';
|
||||
|
||||
try {
|
||||
const body = JSON.stringify({
|
||||
@@ -108,7 +108,7 @@ export function createLLMTools(context: vscode.ExtensionContext): MCPTool[] {
|
||||
return `Need at least 2 providers for consensus. Found: ${providers.map(p => p.name).join(', ') || 'none'}. Configure API keys for multiple providers.`;
|
||||
}
|
||||
const models = args.models || providers.slice(0, 3).flatMap(p => p.models?.slice(0, 1) || [p.name]);
|
||||
return `Consensus query across ${models.length} models: ${models.join(', ')}\n\nTo use consensus, configure the Hanzo LLM gateway at https://llm.hanzo.ai which supports routing to 100+ providers with built-in consensus and fallback.`;
|
||||
return `Consensus query across ${models.length} models: ${models.join(', ')}\n\nTo use consensus, configure the Hanzo LLM gateway at https://api.hanzo.ai which supports routing to 100+ providers with built-in consensus and fallback.`;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user